Array Contains Too Slow Swift - arrays

I have been porting over an algorithm I've been using in Java (Android) to Swift (iOS), and have run into some issues with speed on the Swift version.
The basic idea is there are objects with depths (comment tree), and I can hide and show replies from the dataset by matching against a list of hidden objects. Below is a visualization
Top
- Reply 1
- - Reply 2
- - Reply 3
- Reply 4
and after hiding from the dataset
Top
- Reply 1
- Reply 4
The relevant methods I've converted from Java are as follows
//Gets the "real" position of the index provided in the "position" variable. The comments array contains all the used data, and the hidden array is an array of strings that represent items in the dataset that should be skipped over.
func getRealPosition(position: Int)-> Int{
let hElements = getHiddenCountUpTo(location: position)
var diff = 0
var i = 0
while i < hElements {
diff += 1
if(comments.count > position + diff && hidden.contains(comments[(position + diff)].getId())){
i -= 1
}
i += 1
}
return position + diff
}
func getHiddenCountUpTo(location: Int) -> Int{
var count = 0
var i = 0
repeat {
if (comments.count > i && hidden.contains(comments[i].getId())) {
count += 1
}
i += 1
} while(i <= location && i < comments.count)
return count
}
This is used with a UITableViewController to display comments as a tree.
In Java, using array.contains was quick enough to not cause any lag, but the Swift version calls the getRealPosition function many times when calling heightForRowAt and when populating the cell, leading to increasing lag as more comment ids are added to the "hidden" array.
Is there any way I can improve on the speed of the array "contains" lookups (possibly with a different type of collection)? I did profiling on the application and "contains" was the method that took up the most time.
Thank you

Both Java and Swift have to go through all elements contained in the array. This gets slower and slower as the array gets larger.
There is no a priori reason for Java to fare better, as they both use the exact same algorithm. However, strings are implemented very differently in each language, so that could make string comparisons more expensive in Swift.
In any case, if string comparison slows you down, then you must avoid it.
Easy fix: use a Set
If you want a simple performance boost, you can replace an array of strings with a set of strings. A set in Swift is implemented with a hash table, meaning that you have expected constant time query. In practice, this means that for large sets, you will see better performance.
var hiddenset Set<String> = {}
for item in hidden {
strset.insert(item)
}
For best performance: use a BitSet
But you should be able to do a whole lot better than even a set can do. Let us look at your code
hidden.contains(comments[i].getId()))
If you are always accessing hidden in this manner, then it means that what you have is a map from integers (i) to Boolean values (true or false).
Then you should do the following...
import Bitset;
let hidden = Bitset ();
// replace hidden.append(comments[i].getId())) by this:
hidden.add(i)
// replace hidden.contains(comments[i].getId())) by this:
hidden.contains(i)
Then your code will really fly!
To use a fast BitSet implementation in Swift, include the following in Package.swift (it is free software):
import PackageDescription
let package = Package(
name: "fun",
dependencies: [
.Package(url: "https://github.com/lemire/SwiftBitset.git", majorVersion: 0)
]
)

i think you need the realPosition to link from a tap on a row in the tableview to the source array?
1) make a second array with data only for the tableViewDataSource
copy all visible elements to this new array. create a special ViewModel as class or better struct which only has the nessesary data to display in the tableview. save in this new ViewModel the realdataposition also as value. now you have a backlink to the source array
2) then populate this TableView only from the new datasource
3) look more into the functional programming in swift - there you can nicer go over arrays for example:
var array1 = ["a", "b", "c", "d", "e"]
let array2 = ["a", "c", "d"]
array1 = array1.filter { !array2.contains($0) }
or in your case:
let newArray = comments.filter{ !hidden.contains($0.getId()) }
or enumerated to create the viewmodel
struct CommentViewModel {
var id: Int
var text: String
var realPosition: Int
}
let visibleComments: [CommentViewModel] = comments
.enumerated()
.map { (index, element) in
return CommentViewModel(id: element.getId(), text: element.getText(), realPosition: index)
}
.filter{ !hidden.contains($0.id) }

Related

Search and replace string in 2D Array in Swift

Teaching myself swift, so complete noob here, but I'm far into a project and just know there must be an easier way to achieve something.
I have a 2D array:
var shopArray = [
["theme":"default","price":0,"owned":true,"active":true,"image":UIImage(named: "defaultImage")!,"title":"BUY NOW"],
["theme":"red","price":1000,"owned":false,"active":false,"image":UIImage(named: "redImage")!,"title":"BUY NOW"],
["theme":"blue","price":2000,"owned":false,"active":false,"image":UIImage(named: "blueImage")!,"title":"BUY NOW"],
["theme":"pool","price":3000,"owned":true,"active":false,"image":UIImage(named: "blueImage")!,"title":"BUY NOW"],
["theme":"line","price":4000,"owned":false,"active":false,"image":UIImage(named: "lineImage")!,"title":"BUY NOW"],
["theme":"neon","price":5000,"owned":false,"active":false,"image":UIImage(named: "lineImage")!,"title":"BUY NOW"]]
Where I simply want to create a function that runs and search for all the "owned" keys and make them all "false".
How do you search and replace in Arrays / 2D Arrays. More specifiaclly, what should the func look like?
Thank you!
You don't have a 2D array, you have an Array of Dictionaries.
You can set all of the values for the owned keys by iterating the indices of the Array and updating the values:
shopArray.indices.forEach { shopArray[$0]["owned"] = false }
That is the functional way to do it. You could also do the same operation with a for loop:
for idx in shopArray.indices {
shopArray[idx]["owned"] = false
}
You could do something like this to loopthrough the array replacing the approriate element.
var i = 0
for x in shopArray {
var y = x
y["owned"] = false
shopArray.remove(at: i)
shopArray.insert(y, at: i)
i = i + 1
}
or you could use a while loop to do the same with less code lines.
var y = 0
while y < shopArray.count {
shopArray[y].updateValue(false, forKey: "owned")
y += 1
}
There is proably somthing doable with .contains, but I'm not sure you need that toachive the result you mention above. Play around in a play ground in xcode and try a few different options without doing anything that might cause issues in your project.

How to merge 2 arrays of equal length into a single dictionary with key:value pairs in Godot?

I have been trying to randomize the values in an ordered array (ex:[0,1,2,3]) in Godot. There is supposed to be a shuffle() method for arrays, but it seems to be broken and always returns "null". I have found a workaround that uses a Fisher-Yates shuffle, but the resulting array is considered "unsorted" by the engine, and therefore when I try to use methods such as bsearch() to find a value by it's position, the results are unreliable at best.
My solution was to create a dictionary, comprised of an array containing the random values I have obtained, merged with a second array of equal length with (sorted) numbers (in numerical order) which I can then use as keys to access specific array positions when needed.
Question made simple...
In GDScript, how would you take 2 arrays..
ex: ARRAY1 = [0,1,2,3]
ARRAY2 = [a,b,c,d]
..and merge them to form a dictionary that looks like this:
MergedDictionary = {0:a, 1:b, 2:c, 3:d}
Any help would be greatly appreciated.
Godot does not support "zip" methodology for merging arrays such as Python does, so I am stuck merging them manually. However... there is little to no documentation about how to do this in GDScript, despite my many hours of searching.
Try this:
var a = [1, 2, 3]
var b = ["a", "b", "c"]
var c = {}
if a.size() == b.size():
var i = 0
for element in a:
c[element] = b[i]
i += 1
print("Dictionary c: ", c)
If you want to add elements to a dictionary, you can assign values to the keys like existing keys.

Why and when to use lazy with Array in Swift?

[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3
[1, 2, 3, -1, -2].lazy.filter({ $0 > 0 }).count // => 3
What is the advantage of adding lazy to the second statement. As per my understanding, when lazy variable is used, memory is initialized to that variable at the time when it used. How does it make sense in this context?
Trying to understand the the use of LazySequence in little more detail. I had used the map, reduce and filter functions on sequences, but never on lazy sequence. Need to understand why to use this?
lazy changes the way the array is processed. When lazy is not used, filter processes the entire array and stores the results into a new array. When lazy is used, the values in the sequence or collection are produced on demand from the downstream functions. The values are not stored in an array; they are just produced when needed.
Consider this modified example in which I've used reduce instead of count so that we can print out what is happening:
Not using lazy:
In this case, all items will be filtered first before anything is counted.
[1, 2, 3, -1, -2].filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one
Using lazy:
In this case, reduce is asking for an item to count, and filter will work until it finds one, then reduce will ask for another and filter will work until it finds another.
[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return $0 > 0 })
.reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one
When to use lazy:
option-clicking on lazy gives this explanation:
From the Discussion for lazy:
Use the lazy property when chaining operations:
to prevent intermediate operations from allocating storage
or
when you only need a part of the final collection to avoid unnecessary computation
I would add a third:
when you want the downstream processes to get started sooner and not have to wait for the upstream processes to do all of their work first
So, for example, you'd want to use lazy before filter if you were searching for the first positive Int, because the search would stop as soon as you found one and it would save filter from having to filter the whole array and it would save having to allocate space for the filtered array.
For the 3rd point, imagine you have a program that is displaying prime numbers in the range 1...10_000_000 using filter on that range. You would rather show the primes as you found them than having to wait to compute them all before showing anything.
I hadn't seen this before so I did some searching and found it.
The syntax you post creates a lazy collection. A lazy collection avoids creating a whole series of intermediate arrays for each step of your code. It isn't that relevant when you only have a filter statement it would have much more effect if you did something like filter.map.map.filter.map, since without the lazy collection a new array is created at each step.
See this article for more information:
https://medium.com/developermind/lightning-read-1-lazy-collections-in-swift-fa997564c1a3
EDIT:
I did some benchmarking, and a series of higher-order functions like maps and filters is actually a little slower on a lazy collection than on a "regular" collection.
It looks like lazy collections give you a smaller memory footprint at the cost of slightly slower performance.
Edit #2:
#discardableResult func timeTest() -> Double {
let start = Date()
let array = 1...1000000
let random = array
.map { (value) -> UInt32 in
let random = arc4random_uniform(100)
//print("Mapping", value, "to random val \(random)")
return random
}
let result = random.lazy //Remove the .lazy here to compare
.filter {
let result = $0 % 100 == 0
//print(" Testing \($0) < 50", result)
return result
}
.map { (val: UInt32) -> NSNumber in
//print(" Mapping", val, "to NSNumber")
return NSNumber(value: val)
}
.compactMap { (number) -> String? in
//print(" Mapping", number, "to String")
return formatter.string(from: number)
}
.sorted { (lhv, rhv) -> Bool in
//print(" Sorting strings")
return (lhv.compare(rhv, options: .numeric) == .orderedAscending)
}
let elapsed = Date().timeIntervalSince(start)
print("Completed in", String(format: "%0.3f", elapsed), "seconds. count = \(result.count)")
return elapsed
}
In the code above, if you change the line
let result = random.lazy //Remove the .lazy here to compare
to
let result = random //Removes the .lazy here
Then it runs faster. With lazy, my benchmark has it take about 1.5 times longer with the .lazy collection compared to a straight array.

Best way to find and match two integers from different arrays in swift 3? (Multidimensional Array)

In Swift 3 i have two multi dimensional arrays named json and json2 that gets some code from an api. I then do an for each on the first array, and for each value inside that array i loop over array 2 to find matches, this seems like a really bad way of doing this, i was hoping for an alternative way that's probally quicker?
Heres my code:
var json:[[String:Any]] = []
var json2:[[String:Any]] = []
for each in self.json {
for each2 in self.json2 {
if(each["PrePaidCoffeeCardId"]! as! Int == each2["id"]! as! Int)
{
print(each2["coffeeBrandId"]! as! Int)
}
}
}
this returns the output in the console:
2
2
3
this is the values i want to get, but the approach of getting them seems to take alot of power, i was hoping for an easier solution?
If you have large numbers of prepaid cards, you will get good performance by building a temporary index using a dictionary :
var cardBrands:[Int:[Int]] = [:]
json2.map{ ($0["id"]! as! Int, $0["coffeeBrandId"]! as! Int) }
.forEach{ cardBrands[$0] = [$1] + (cardBrands[$0] ?? Array<Int>()) }
let brandIds = json.flatMap{ cardBrands[ $0["PrePaidCoffeeCardId"]! as! Int]! }
The cardBrands dictionary is setup only once, and returns the list of coffeeBrandIds of each card id. Because of the dictionary's internal indexing it will provide much faster results than iterating through the json2 array. This will give you roughly N+M processing time instead of N*M.

Nested Swift Dictionaries and Arrays like a Hash Perl

I'm having a horrible time understanding a few things about Swift.
I am attempting to do scripting in Swift to see if it might be useful. One of the major runs of scripting file is how easy the language gets data in from text-files and the hash/dictionary/array parsing syntax.
In trying to parse huge csv files, I'm trying to create a matrix Dictionary that mimics a perl method ${$hash}{$var1}{$var2}{$var3} = $value; where #house_hash is the actual hash and each of the keys are dynamically created and added if not present already.
in ObjectiveC, this was simple enough, using NSMutableDictionary, atleast the retrieval part of it.
Swift seems pretty exciting, i wonder with how much ease can this be done?
Till now, all I have is:
var hash = [Int : [Int : [String : AnyObject]]] ()
let number = hash[var1]?[var2]?["var3"]? .hashValue
var d3 = [String : AnyObject]()
var d2 = [Int : [String : AnyObject]] ()
d3["var3"] = var3 //var3 is a float
if(number == nil)
{
d2[var2] = d3
hash[var1] = d2
}
else
{
hash[var1]![var2] = d3
}
Absolutely no idea what I'm doing. There are no good examples if this dynamic nature of Swift dictionaries that house unknown object types (could be arrays, Ints, Strings, another Dictionary)..
UPDATE: I found this SO answer. But it does not seem to work in Swift Playground.

Resources