Best way to loop through an array as fast as possible? - ios

Right now, my app retrieves around 1000 CKRecords which gets put into an array. I need to loop through this array later on. When I just had about 100 records to loop through my app worked smoothly and fast, but now it gets slower and slower as the number of records grow. I plan to have around 3000 CKRecords to retrieve so this could be a serious problem for me.
This is what I have come up with for now:
//movies == The NSMutableArray
if let recordMovies = movies.objectEnumerator().allObjects as? [CKRecord] {
for movie in recordMovies {
//Looping
print(movie.objectForKey("Title") as? String)
{
}
But this doesn´t help a lot, seeing as I have to turn the NSMutableArray into an Array of CKRecords. I do this because I have to access the CKRecords attributes when looping, such as movieTitle. What measures can I take to speed up the looping process

Iterating through 1000 (or 5000) elements of an array should take a tiny fraction of a second.
Printing to the debug console is very slow however. Get rid if your print statement.
If movies is an array of CKRecord objects, why can't you just cast it to the desired array type?
let start = NSDate.timeIntervalSinceReferenceDate()
if let recordMovies = movies as? [CKRecord] {
for movie in recordMovies {
//Do whatever work you need to do on each object, but DON'T use print
{
}
let elapsed = NSDate.timeIntervalSinceReferenceDate() - start
print( "iterating through the loop took \(elapsed) seconds")

You can save a lot of time by making "movies" not just an NSMutableArray, but an NSMutableArray of CKRecords, which makes it unnecessary to convert the array from NSMutableArray to a Swift array of CKRecord.
In Objective-C: Instead of
NSMutableArray* movies;
you use
NSMutableArray <CKRecord*> *movies;
As a result, movies [1] for example will have type CKRecord*, not id. An assignment movies [1] = something; requires that "something" is of type CKRecord* or id, nothing else.

Related

Realm iOS: count the distinct objects in a collection

What is the most efficient way to get the count of the unique values on a table?
For example:
fruitType
---------
banana
banana
apple
apple
apple
bananas : 2
apples : 3
By using fruitsCollection.distinct(by: ["fruitType"]) i can get the distinct values but not the count.
Any help would be appreciated.
you could try something simple like this (assuming fruits are Strings, adjust accordingly to your object types):
let fruits = fruitsCollection.distinct(by: ["fruitType"])
var results = [String:Int]()
Set(fruits).forEach{ fruit in results[fruit] = (fruits.filter{$0 == fruit}).count }
print("---> results: \(results)")
or
let results: [String:Int] = Set(fruits).reduce(into: [:]) { dict, next in
dict[next] = (fruits.filter{$0 == next}).count }
print("---> results: \(results)")
The Set(fruits) gives you the unique set of fruit names. The filter{...} gives you the count of each. The forEach or the reduce turns the results into a dictionary of key values.
#workingdog answer works very well but here's a more Realmy option. Something to keep in mind is that Realm Results objects are lazily loaded - meaning working with very large datasets has a low memory impact.
However, as soon high level Swift functions are used, that lazy-ness is lost and every object gobbles up memory.
For example, loading 50,000 Realm objects into a Results object doesn't have any significant memory impact - however, loading 50,000 objects into an Array could overwhelm the device as the objects loose their lazy-loading nature.
With this solution, we rely on Realm to present unique values and store them in Results (lazy!) then iterating over those we filter for matching objects (lazy) and return their count.
I created a FruitClass to hold the fruit types
class FruitClass: Object {
#Persisted var fruitType = ""
}
and then to code
This is a very memory friendly solution
//get the unique types of fruit. results are lazy!
let results = realm.objects(FruitClass.self).distinct(by: ["fruitType"])
//iterate over the results to get each fruit type, and then filter for those to get the count of each
for fruit in results {
let type = fruit.fruitType
let count = realm.objects(FruitClass.self).filter("fruitType == %#", type).count
print("\(type) has a count of \(count)")
}
and the results
apple has a count of 3
banana has a count of 2
orange has a count of 1
pear has a count of 1

Storing and working with huge dictionaries in Swift

I am trying to load a dictionary with around 11,000 pairs into a Swift program (about .7MB). The values of the dictionary are arrays, and I need to be able to loop through the dictionary's arrays and compare those values with values in another array. There is an average of 10 items in the subarrays, although some have two or three, and some have hundreds.
The program crashes, and I am looking for a way to fix it while keeping the functionality from my Python-based prototype. I was thinking of using a bunch of property lists, and loading them into memory one by one, but this seems inefficient. What can I do to redesign this?
Some of the code which is being problematic:var dictOfHashesAndMaterials:[String:[Int]]: = ["10928123sdfb234w3fw3": [123,435,1,573,456,3234,653,57856,434,345],"13435fw323df0239dfsdf":[435,756,978,231,5668,485,9678,1233,87,989]] // and 11,000 additional entries which look similar to this.
And here's where I use it:` // function to populate the list of ingredients.
func populateListOfRecipes(){
var storedIngredients = UserDefaults.standard.object(forKey: "ingredients") as! [Int]
for(key, value) in dictOfHashesAndMaterials{
let listSet = Set(storedMaterials)
let findListSet = Set(value)
if findListSet.isSubset(of: listSet){
print("found a match!")
arrayOfAvailableProjects.append(key)
}
}
tableView.reloadData()
}`
A tableView is then populated by cells which link to the specifics of a given project. The details are filled in when the hashes are sent to the server, and the corresponding instructions are returned in full to the app.
Is it crashing in the simulator? I've worked with 30 GB files using the simulator without a crash. On the device is a different story...
Anyway, try this:
for(key, value) in dictOfHashesAndMaterials{
autoreleasepool {
let listSet = Set(storedMaterials)
let findListSet = Set(value)
if findListSet.isSubset(of: listSet){
print("found a match!")
arrayOfAvailableProjects.append(key)
}
}
}
tableView.reloadData()
}
By draining the auto release pool every iteration you will prevent running out of available memory.

Iterate through an optional array in Swift?

I'm using the cocoapod SQLite for this project like so
import SQLite
var db = try! Connection()
var id: Expression<Int>!
var identifier: Expression<String>!
With it I am reading a list of moves from a SQLite database.
Every monster has a moves that they can learn. Some monsters can learn more moves than others.
var monster: Monster!
var monArray = [Monster]()
var dataSource: DataSource!
To get the monsters move ID I use this code. This allows me to grab the first move in the array. Changing the 0 would get me the second, third move ect.
monster.moves![0]["move_id"] as! Int
Now I'm using the SQLite database because I need to match monster ID values in my plist with the ones in the SQLite database. I use this code to do so
override func viewWillAppear(_ animated: Bool) {
let movesArray = Array(try! db.prepare(Table("moves").where(identifier == moves.name!.lowercased())))
for user in movesArray {
monArray = dataSource.mons.filter{ $0.moves![0]["move_id"] as! Int == user[id] }
}
}
Everything works fine until I try to increase the index range.
for user in movesArray {
for i in 0...6 {
monArray = dataSource.mons.filter{ $0.moves![i]["move_id"] as! Int == user[id] }
}
}
See where I replace the 0 with the range i? I do that because since monsters have more than one move, if I leave it at 0 my app will only display the monsters that learn that move as their first move. To better explain, my current code does not look through whether the monster knows the move, it only looks through whether the monsters knows the move as its first move.
In the above code I increase the range thinking it would solve my issue, but my app will crash because some monsters only have 1 move in their index, so anything above index 0 will crash with the error
fatal error: Index out of range
So to recap, I need to iterate through the entire array instead of just the first index, without it crashing. How can I achieve this?
Without all the story arround your just asking how to iterate over an array like here
for item in array as type {
...
}
Your question seems to be more of a logical question. If I'm understanding what you have said, then each monster will have a minimum of 1 move, but not guaranteed to have more. So you would need to account for this.
Having a fixed limit like you do will certainly cause problems if not all monsters have that many moves as the array will not always be that size.
I would do something like:
let monsterMoveCount = user.moves.count
for i in 0...monsterMoveCount
// Do whatever logic here
Hopefully this helps!

Realm: live updates of constant values

I'm using SwiftRealm 2.03 and do not understand the magic how constant data (even metadata) gets updated if the data in realm changes...
Here an example:
private func closePastExistingTravelTimes(){
let travelTimes = fetchTravelTimes(onlyNotClosedTravelTimes: true)
guard travelTimes.count > 1 else {
return
}
let numberOfTravelTimes = travelTimes.count
for index in 0..<numberOfTravelTimes-2{
print("index:\(index) count:\(travelTimes.count)")
try! realm.write {
let travelTime = travelTimes[index]
travelTime.travelPhaseIsClosed = true
realm.add(travelTime, update: true)
}
}
}
I'm loading data in the beginning and store them in an constant.
Then I iterate over the items and change the condition of the query so that the fetched data would change if I would query again. But I don't. What even is more suprising that the constant numberOfTravelTimes is even adjusted as you can see below in the log.
index:0 count:5
index:1 count:4
index:2 count:3
index:3 count:2 --> BAM - Exception
What is happening here? How can I be save in my example?
Realm Results objects are live, meaning if you update an object in such a way that it no longer conforms to a Results query, the Results object will update to exclude it. This means you need to be careful when you base a for loop off a Results object, since if the Results object mutates in the middle of the loop, you'll end up with an exception.
Normally, the easiest, but not the most elegant way to mitigate this is to copy all of the Realm objects in a Results object to a static array so it won't mutate during the loop.
In this particular case however, it would be more appropriate to just enclose the entire for loop inside the Realm write transaction. This is generally best practice (Since it's best to batch as many Realm writes into as few write transactions as possible), but in this case, it will have the added advantage of not updating the contents of the Results object until after you're done with it.
try! realm.write { // Open the Realm write transaction outside of the loop
for index in 0..<numberOfTravelTimes-2 {
print("index:\(index) count:\(travelTimes.count)")
let travelTime = travelTimes[index]
travelTime.travelPhaseIsClosed = true
realm.add(travelTime, update: true)
}
}
You should iterate your results in reverse order.
for index in (0 ..<numberOfTravelTimes-1).reverse()
See if it helps

Core Data in Swift: Comparing objects in a for loop

I'm trying to update the user's music listen history by checking to see if any MPMediaItemPropertyPlayCounts have changed since the last check. Right now, upon each check, I'm querying the entire iPod library and comparing their existing play counts already in Core Data. If current play count != play count stored in Core Data, we do something with that song, since we know the user has listened to it recently.
In my code, I'm struggling to loop through all iPod songs and search for corresponding Core Data objects simultaneously. The code below prints out way too many lines. How do I search for objects in Core Data in a for loop?
class func checkiPodSongsForUpdate() {
var appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
var context: NSManagedObjectContext = appDel.managedObjectContext!
var newSong = NSEntityDescription.insertNewObjectForEntityForName("IPodSongs", inManagedObjectContext: context) as! NSManagedObject
var request = NSFetchRequest(entityName: "IPodSongs")
request.returnsObjectsAsFaults = false
var results = context.executeFetchRequest(request, error: nil)
let query = MPMediaQuery.songsQuery()
let queryResult = query.collections as! [MPMediaItemCollection]
for song in queryResult {
for song in song.items as! [MPMediaItem] {
request.predicate = NSPredicate(format: "title = %#", "\(song.valueForProperty(MPMediaItemPropertyTitle))")
for result: AnyObject in results! {
if let playCount = result.valueForKey("playCount") as? String {
if playCount != "\(song.valueForProperty(MPMediaItemPropertyPlayCount))" {
println("\(song.valueForProperty(MPMediaItemPropertyTitle)) has a new play count.")
} else {
println("User hasn't listened to this song since last check.")
}
}
}
}
}
}
The immediate problem is that you assign a value to request.predicate, but doing so has no effect. A fetch request predicate only has effect if you assign it before doing the fetch. It becomes part of the fetch, and the fetch results only include objects that match the predicate.
There are a couple of possibilities for fixing this specific problem:
Do your Core Data fetch in the loop after assigning the predicate, inside the loop. This would get the results you expect. However it will also be extremely inefficient and won't scale well. Fetch requests are relatively expensive operations, so you'll probably find that you're spending a lot of time doing them.
Instead of assigning a value to request.predicate, filter the results array in memory by doing something like
let predicate = NSPredicate(format: "title = %#", "\(song.valueForProperty(MPMediaItemPropertyTitle))")
let currentResults = results.filteredArrayUsingPredicate(predicate)
That will get you an array of only the songs matching the predicate. This will be faster than option #1, but it still leaves the major problem that you're fetching all of the songs here. This potentially means loading a ton of managed objects into memory every time you call this function (suppose the user has, say, 50,000 songs?). It's still not a good solution. Unfortunately I don't know enough about MPMediaQuery to recommend a better approach.
Either of these should fix the immediate problem, but you need to address the more significant conceptual issue. You're comparing two potentially large collections looking for a single match. That's going to be a lot of CPU time or memory or both no matter how you approach it.
Found my own solution. Looping through two arrays can get expensive with enough objects in each array, just as #Tom said. I decided to loop through arrayOne and inside that loop, call a custom function that looks for the given key/value pair in arrayTwo.
// Custom function that returns Bool if arrayTwo contains a given key/value pair from inside the arrayOne loop
func arrayContains(array:[[String:String]], #key: String, #value: String) -> Bool {
for song in array {
if song[key] == value {
return true
}
}
return false
}
Big thanks to #vacawama for the help here: Find key-value pair in an array of dictionaries

Resources