How Can I Construct an Efficient CoreData Search, Including Allowing For Preceding and Trailing Characters Here? - ios

Based on straight SQL searches in a previous app, I am adding CoreData searching to a new app. These searches are in a custom dictionary db that the app contains; this function does the work:
public func wordMatcher (pad: Int, word: Array<String>, substitutes : Set<String> ) {
let context = CoreDataManager.shared.persistentContainer.viewContext
var query: Array<String>
var foundPositions : Set<Int> = []
var searchTerms : Array<String> = []
if word.count >= 4 {
for i in 0..<word.count {
for letter in substitutes {
query = word
query[i] = letter
searchTerms.append(query.joined())
let rq: NSFetchRequest<Word> = Word.fetchRequest()
rq.predicate = NSPredicate(format: "name LIKE %#", query.joined())
rq.fetchLimit = 1
do {
if try context.fetch(rq).count != 0 {
foundPositions.insert(i)
break
}
} catch {
}
}
// do aggregated searchTerms search here instead of individual searches?
}
}
}
The NSFetchRequest focuses on one permutation at a time. But I'm accumulating the search string fragments in the array searchTerms because I don't know if it would be more efficient to construct a single query connected with ORs, and I also don't know how to do that in CoreData.
The focus is on the positions in the original term word: I need to indicate if any given location has at least one of the substitutes as a valid fit. So to implement the aggregate searchTerms approach, a FetchRequest would have to happen for each location in the base term.
A second complication is the one referred to in the title of the question. I am using LIKE because the search term in the FetchRequest could be a substring in a longer word. However, the maximum number of letters is 11, and pad is the starting point of the original term in that field of 11 spaces.
So if pad is 3, then I would need to allow for 0..<pad preceding characters. And because there may be trailing characters, I would also want results with 0..<(11 - (pad + word.count)) alphabetic characters after the last letter in the search term.
Regex seems like one way to do this, but I haven't found a clear example of how to do this in this case, and especially with the multiple search terms (if that's the way to go). The limits of SQLite in the previous version forced constructing multiple queries with increasing numbers of "_" underscores to indicate the padding characters; that tended to really explode the number of queries.
BTW, substitutes is limited to an absolute maximum of 9 values, and in practice is usually below 5, so things are a little more manageable.
I would like to get a grip on this, and so if anyone can provide direction or examples that can make this a reasonably efficient function, the help is appreciated greatly.
EDIT:
I've realized that I need a result for each position in the target string, with cases where the leading and trailing spaces also may need to contain a substitute as well.
So I'm moving to this:
public func wordMatcher (pad: Int, word: Array<String>, substitutes : Set<String> ) {
let context = CoreDataManager.shared.persistentContainer.viewContext
var pad_ = pad
var query: Array<String>
var foundPositions : Set<Int> = []
let rq: NSFetchRequest<Word> = Word.fetchRequest()
rq.fetchLimit = 1
let subs = "[\(substitutes.joined())]"
// if word.count >= 4 { // because those locations will be blocked off anyway otherwise
let start = pad > 0 ? -1 : 0
let finish = 11 - (pad + word.count) > 0 ? word.count + 1 : word.count
for i in start..<finish {
query = word
var _pad = 11 - (pad + word.count)
if i == -1 {
query = Array(arrayLiteral: subs) + query
pad_ -= 1
} else if i > word.count {
query.append(subs)
_pad -= 1
} else {
pad_ = pad
query[i] = subs
}
let endPad = _pad > 0 ? "{0,\(_pad)}" : ""
let predMatch = ".\(query.joined())\(endPad)"
print(predMatch)
rq.predicate = NSPredicate(format:"position <= %# AND word MATCHES %#", pad_, predMatch)
do {
if try context.fetch(rq).count != 0 {
foundPositions.insert(i)
}
} catch {
}
// }
}
lFreq = foundPositions
}
This relies on a regex substitution, inserted into the original target string. What I'll have to find out is if this is fast enough at the edge cases, but it may not be critical even in the worst case.
predMatch will end up looking something like "ab[xyx]d{0,3}", and I think I can get rid of the position section by changing it to be "{0,2}ab[xyx]d{0,3}". But I guess I'm going to have to try to find out.

Related

Comparing quantities of unique elements contained in large data sets [duplicate]

This question already has answers here:
Refactored Solution In Swift
(2 answers)
Closed 6 years ago.
I'm attempting to solve HackerRank's Hash Table Ransom Note challenge. There are 19 test cases and I'm passing all but two of time due to timeout on larger data sets (10,000-30,000 entries).
I'm given:
1) an array of words contained in a magazine and
2) an array of words for a ransom note. My objective is to determine if the words in the magazine can be used to construct a ransom note.
I need to have enough unique elements in the magazineWords to satisfy the quantity needed by noteWords.
I'm using this code to make that determination...and it takes FOREVER...
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
What is a faster way to accomplish this task?
Below is my complete code for the challenge:
import Foundation
var magazineWords = // Array of 1 to 30,000 strings
var noteWords = // Array of 1 to 30,000 strings
enum RegexString: String {
// Letters a to z, A to Z, 1 to 5 characters long
case wordCanBeUsed = "([a-zA-Z]{1,5})"
}
func matches(for regexString: String, in text: String) -> [String] {
// Hat tip MartinR for this
do {
let regex = try NSRegularExpression(pattern: regexString)
let nsString = text as NSString
let results = regex.matches(in: text, range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range)}
} catch let error {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
func canCreateRansomNote(from magazineWords: [String], for noteWords: [String]) -> String {
// figure out what's unique
let magazineWordsSet = Set(magazineWords)
let noteWordsSet = Set(noteWords)
let intersectingValuesSet = magazineWordsSet.intersection(noteWordsSet)
// constraints specified in challenge
guard magazineWords.count >= 1, noteWords.count >= 1 else { return "No" }
guard magazineWords.count <= 30000, noteWords.count <= 30000 else { return "No" }
// make sure there are enough individual words to work with
guard magazineWordsSet.count >= noteWordsSet.count else { return "No" }
guard intersectingValuesSet.count == noteWordsSet.count else { return "No" }
// check if all the words can be used. assume the regex method works perfectly
guard noteWords.count == matches(for: RegexString.wordCanBeUsed.rawValue, in: noteWords.joined(separator: " ")).count else { return "No" }
// FIXME: this is a processor hog. I'm timing out when I get to this point
// need to make sure there are enough magazine words to write the note
// compare quantity of word in magazine with quantity of word in note
for word in noteWordsSet {
// check if there are enough unique words in magazineWords to put in the note
if magazineWords.filter({$0==word}).count < noteWords.filter({$0==word}).count {
return "No"
}
}
return "Yes"
}
print(canCreateRansomNote(from: magazineWords, for: noteWords))
I don't know how to read from the test case on the contest website or what frameworks you are allowed. If Foundation is allowed, you can use NSCountedSet
import Foundation
let fileContent = try! String(contentsOf: URL(fileURLWithPath: "/path/to/file.txt"))
let scanner = Scanner(string: fileContent)
var m = 0
var n = 0
scanner.scanInt(&m)
scanner.scanInt(&n)
var magazineWords = NSCountedSet(capacity: m)
var ransomWords = NSCountedSet(capacity: n)
for i in 0..<(m+n) {
var word: NSString? = nil
scanner.scanUpToCharacters(from: .whitespacesAndNewlines, into: &word)
if i < m {
magazineWords.add(word!)
} else {
ransomWords.add(word!)
}
}
var canCreate = true
for w in ransomWords {
if ransomWords.count(for: w) > magazineWords.count(for: w) {
canCreate = false
break
}
}
print(canCreate ? "Yes" : "No")
It works by going through the input file one word at a time, counting how many times that word appears in the magazine and in the ransom note. Then if any word appear more frequently in the ransom note than in the magazine, it fails the test immediately. Run the 30,000 words test case in less than 1 second on my iMac 2012.

Refactored Solution In Swift

I've been studying for a coding exam by doing the HackerRank test cases, for the most part I've been doing well, but I get hung up on some easy cases and you all help me when I can't see the solution. I'm working on this problem:
https://www.hackerrank.com/challenges/ctci-ransom-note
A kidnapper wrote a ransom note but is worried it will be traced back to him. He found a magazine and wants to know if he can cut out whole words from it and use them to create an untraceable replica of his ransom note. The words in his note are case-sensitive and he must use whole words available in the magazine, meaning he cannot use substrings or concatenation to create the words he needs.
Given the words in the magazine and the words in the ransom note, print Yes if he can replicate his ransom note exactly using whole words from the magazine; otherwise, print No.
Input Format
The first line contains two space-separated integers describing the respective values of (the number of words in the magazine) and (the number of words in the ransom note).
The second line contains space-separated strings denoting the words present in the magazine.
The third line contains space-separated strings denoting the words present in the ransom note.
Each word consists of English alphabetic letters (i.e., to and to ).
The words in the note and magazine are case-sensitive.
Output Format
Print Yes if he can use the magazine to create an untraceable replica of his ransom note; otherwise, print No.
Sample Input
6 4
give me one grand today night
give one grand today
Sample Output
Yes
Explanation
All four words needed to write an untraceable replica of the ransom note are present in the magazine, so we print Yes as our answer.
And here is my solution:
import Foundation
func main() -> String {
let v = readLine()!.components(separatedBy: " ").map{Int($0)!}
var a = [String](); var b = [String]()
if v[0] < v[1] { return "No"}
for i in 0 ..< 2 {
if i == 0 {
a = (readLine()!).components(separatedBy: " ")
} else { b = (readLine()!).components(separatedBy: " ") }
}
// Get list of elements that intersect in each array
let filtered = Set(a).intersection(Set(b))
// Map set to set of Boolean where true means set a has enough words to satisfy set b's needs
let checkB = filtered.map{ word in reduceSet(b, word: word) <= reduceSet(a, word: word) }
// If mapped set does not contain false, answer is Yes, else No
return !checkB.contains(false) ? "Yes" : "No"
}
func reduceSet(_ a: [String], word: String) -> Int {
return (a.reduce(0){ $0 + ($1 == word ? 1 : 0)})
}
print(main())
I always time out on three of the 20 test-cases with this solution. So the solution seems to solve all the test cases, but not within their required time constraints. These are great practice, but it's so extremely frustrating when you get stuck like this.
I should note that I use Sets and the Set(a).intersection(Set(b)) because when I tried mapping an array of Strings, half the test-cases timed out.
Any cleaner, or more efficient solutions will be greatly appreciated! Thank you!
Thanks to #Alexander - I was able to solve this issue using NSCountedSet instead of my custom reduce method. It's much cleaner and more efficient. Here is the solution:
import Foundation
func main() -> String {
let v = readLine()!.components(separatedBy: " ").map{Int($0)!}
var a = [String](); var b = [String]()
if v[0] < v[1] { return "No"}
for i in 0 ..< 2 {
if i == 0 {
a = (readLine()!).components(separatedBy: " ")
} else { b = (readLine()!).components(separatedBy: " ") }
}
let countA = NSCountedSet(array: a)
let countB = NSCountedSet(array: b)
let intersect = Set(a).intersection(Set(b))
let check = intersect.map{ countB.count(for: $0) <= countA.count(for: $0) }
return !check.contains(false) ? "Yes" : "No"
}
print(main())
Many thanks!
I took the leisure of making some improvements on your code. I put comments to explain the changes:
import Foundation
func main() -> String {
// Give more meaningful variable names
let firstLine = readLine()!.components(separatedBy: " ").map{Int($0)!}
let (magazineWordCount, ransomNoteWordCount) = (firstLine[0], firstLine[1])
// a guard reads more like an assertion, stating the affirmative, as opposed to denying the negation.
// it also
guard magazineWordCount > ransomNoteWordCount else { return "No" }
// Don't use a for loop if it only does 2 iterations, which are themselves hardcoded in.
// Just write the statements in order.
let magazineWords = readLine()!.components(separatedBy: " ")
let ransomNoteWords = readLine()!.components(separatedBy: " ") //You don't need ( ) around readLine()!
let magazineWordCounts = NSCountedSet(array: magazineWords)
let ransomNoteWordCounts = NSCountedSet(array: ransomNoteWords)
// intersect is a verb. you're looking for the noun, "intersection"
// let intersection = Set(a).intersection(Set(b))
// let check = intersect.map{ countB.count(for: $0) <= countA.count(for: $0) }
// You don't actually care for the intersection of the two sets.
// You only need to worry about exactly the set of words that
// exists in the ransom note. Just check them directly.
let hasWordWithShortage = ransomNoteWordCounts.contains(where: { word in
magazineWordCounts.count(for: word) < ransomNoteWordCounts.count(for: word)
})
// Don't negate the condition of a conditional expression. Just flip the order of the last 2 operands.
return hasWordWithShortage ? "No" : "Yes"
}
print(main())
with the comments removed:
import Foundation
func main() -> String {
let firstLine = readLine()!.components(separatedBy: " ").map{Int($0)!}
let (magazineWordCount, ransomNoteWordCount) = (firstLine[0], firstLine[1])
guard magazineWordCount > ransomNoteWordCount else { return "No" }
let magazineWords = readLine()!.components(separatedBy: " ")
let ransomNoteWords = readLine()!.components(separatedBy: " ")
let magazineWordCounts = NSCountedSet(array: magazineWords)
let ransomNoteWordCounts = NSCountedSet(array: ransomNoteWords)
let hasWordWithShortage = ransomNoteWordCounts.contains{ word in
magazineWordCounts.count(for: word) < ransomNoteWordCounts.count(for: word)
}
return hasWordWithShortage ? "No" : "Yes"
}
print(main())
It's simpler, and much easier to follow. :)

What is a fast way to convert a string of two characters to an array of booleans?

I have a long string (sometimes over 1000 characters) that I want to convert to an array of boolean values. And it needs to do this many times, very quickly.
let input: String = "001"
let output: [Bool] = [false, false, true]
My naive attempt was this:
input.characters.map { $0 == "1" }
But this is a lot slower than I'd like. My profiling has shown me that the map is where the slowdown is, but I'm not sure how much simpler I can make that.
I feel like this would be wicked fast without Swift's/ObjC's overhead. In C, I think this is a simple for loop where a byte of memory is compared to a constant, but I'm not sure what the functions or syntax is that I should be looking at.
Is there a way to do this much faster?
UPDATE:
I also tried a
output = []
for char in input.characters {
output.append(char == "1")
}
And it's about 15% faster. I'm hoping for a lot more than that.
This is faster:
// Algorithm 'A'
let input = "0101010110010101010"
var output = Array<Bool>(count: input.characters.count, repeatedValue: false)
for (index, char) in input.characters.enumerate() where char == "1" {
output[index] = true
}
Update: under input = "010101011010101001000100000011010101010101010101"
0.0741 / 0.0087, where this approach is faster that author's in 8.46 times. With bigger data correlation more positive.
Also, with using nulTerminatedUTF8 speed a little increased, but not always speed higher than algorithm A:
// Algorithm 'B'
let input = "10101010101011111110101000010100101001010101"
var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
for (index, code) in input.nulTerminatedUTF8.enumerate() where code == 49 {
output[index] = true
}
In result graph appears, with input length 2196, where first and last 0..1, A – second, B – third point.
A: 0.311sec, B: 0.304sec
import Foundation
let input:String = "010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010"
var start = clock()
var output = Array<Bool>(count: input.nulTerminatedUTF8.count, repeatedValue: false)
var index = 0
for val in input.nulTerminatedUTF8 {
if val != 49 {
output[index] = true
}
index+=1
}
var diff = clock() - start;
var msec = diff * 1000 / UInt(CLOCKS_PER_SEC);
print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");
This should be really fast. Try it out. For 010101011010101001000100000011010101010101010101 it takes 0.039 secs.
I would guess that this is as fast as possible:
let targ = Character("1")
let input: String = "001" // your real string goes here
let inputchars = Array(input.characters)
var output:[Bool] = Array.init(count: inputchars.count, repeatedValue: false)
inputchars.withUnsafeBufferPointer {
inputbuf in
output.withUnsafeMutableBufferPointer {
outputbuf in
var ptr1 = inputbuf.baseAddress
var ptr2 = outputbuf.baseAddress
for _ in 0..<inputbuf.count {
ptr2.memory = ptr1.memory == targ
ptr1 = ptr1.successor()
ptr2 = ptr2.successor()
}
}
}
// output now contains the result
The reason is that, thanks to the use of buffer pointers, we are simply cycling through contiguous memory, just like the way you cycle through a C array by incrementing its pointer. Thus, once we get past the initial setup, this should be as fast as it would be in C.
EDIT In an actual test, the time difference between the OP's original method and this one is the difference between
13.3660290241241
and
0.219357967376709
which is a pretty dramatic speed-up. I hasten to add, however, that I have excluded the initial set-up from the timing test. This line:
let inputchars = Array(input.characters)
...is particularly expensive.
This should be a little faster than the enumerate() where char == "1" version (0.557s for 500_000 alternating ones and zeros vs. 1.159s algorithm 'A' from diampiax)
let input = inputStr.utf8
let n = input.count
var output = [Bool](count: n, repeatedValue: false)
let one = UInt8(49) // 1
for (idx, char) in input.enumerate() {
if char == one { output[idx] = true }
}
but it's also a lot less readable ;-p
edit: both versions are slower than the map variant, maybe you forgot to compile with optimizations?
One more step should speed that up even more. Using reserveCapacity will resize the array once before the loops starts instead of trying to do it as the loop runs.
var output = [Bool]()
output.reserveCapacity(input.characters.count)
for char in input.characters {
output.append(char == "1")
}
Use withCString(_:) to retrieve a raw UnsafePointer<Int8>. Iterate over that and compare to 49 (ascii value of "1").
What about a more functional style? It's not fastest (47 ms), today, for sure...
import Cocoa
let start = clock()
let bools = [Bool](([Character] ("010101011001010101001010101100101010100101010110010101010101011001010101001010101100101010100101010101011001010101001010101100101010100101010".characters)).map({$0 == "1"}))
let msec = (clock() - start) * 1000 / UInt(CLOCKS_PER_SEC);
print("Time taken \(Double(msec)/1000.0) seconds \(msec%1000) milliseconds");
I need to some testing to be sure but I think one issue with many approaches given including the original map is that they need to iterate over the string to count the characters and then a second time to actually process the characters.
Have you tried:
let output = [Bool](input.characters.lazy.map { $0 == "1" })
This might only do a single iteration.
The other thing that could speed things up is if you can avoid using strings but instead use arrays of characters of an appropriate encoding (particularly if is more fixed size units (e.g. UTF16 or ASCII). Then then length lookup will be O(1) rather than O(n) and the iteration may be faster too
BTW always test performance with the optimiser enabled and never in the Playground because the performance characteristics are completely different, sometimes by a factor of 100.

fastest indexOf function for strings

I am currently using following extension for a string to get the index for a specific string in a big string:
func indexOf(target: String, startIndex: Int) -> Int
{
var startRange = advance(self.startIndex, startIndex)
var range = self.rangeOfString(target, options: NSStringCompareOptions.LiteralSearch, range: Range<String.Index>(start: startRange, end: self.endIndex))
if let range = range {
return distance(self.startIndex, range.startIndex)
} else {
return -1
}
}
I am calling this many times and I have a performance issue.
Does anyone have an idea how to do the indexOf() faster ?
Currently I am doing this in swift. Will doing this in Objective-C and bridging give a better performance ? Or probably if possible include any C Code ? Any ideas ?
UPDATE more about the Background
I have a long text, say with 5000 characters.
The Text contains several Metadata tags beside from normal text. These Tags are like {{blabl{{ sdasdg }} abla}} ; [[bla bla|blabla]] ; {|bla|}.
I like to remove them or format them in a specific way.
I can't use regular expression for this, because regular expression does not support stacked expressions ({{ {{ {{ {{dsgasdg}} }}}} }} )
So I wrote my own functions, which works, but is very slow.
What I am actually doing is I go throught the text and I am simply searchiong for these tags. For this I need a base function like the following, to determine which tag is the first and at which position. When I found a tag I will go to the next and so on. I recognized, that this is my most timeconsuming part of all. Of course I am calling this also a lot of time.
func getStart(sText:String, alSearchPatterns:[String], ifrom:Int) -> (Pattern:String, index:Int) {
var bweiter:Bool=true;
var actualcharacter:Character;
var returnPattern="";
var returnIndex = -1;
println("ifrom : " + String(ifrom));
var indexfound:Int = -1;
// finde ersten character der Patterns
var bsuchepattern=true;
for(var i=0;i<alSearchPatterns.count && bsuchepattern;i++){
let sPattern=alSearchPatterns[i];
let pattern_first_char=sPattern[0];
//let pattern_first_char=String(sPattern[0]);
let characterIndex = sText.indexOfCharacter(pattern_first_char, fromIndex: ifrom); // find(sText, pattern_first_char);
//let characterIndex = sText.indexOf(pattern_first_char, startIndex: ivon);
if(characterIndex != -1){
if((indexfound == -1) || characterIndex < indexfound){
// found something that is first of all actually.
let patternlength=sPattern.length;
let substring_in_text=sText.substring(characterIndex, endIndex: characterIndex + patternlength);
if(substring_in_text.equals(sPattern)){
returnPattern=sPattern;
returnIndex=characterIndex;
}
}
}
}
return (returnPattern,returnIndex);
}
Any hints how to do this more performant or any hints on how to do this better in general.

This algorithm takes more time than expected

I have this code, where i would call this "checkingfunction" function. I am not using any threading in my app, I would love to use if it benefits the performance of my app.
The "checkingfunction", takes more time than i expected. It takes more than 30 seconds to complete the execution. I cant wait that long in my app. That is not good, in middle of the game.
Somebody help me out here to rewrite the function, so that i can execute it in a faster way. Some functional programming way, if possible.
func returnCharactersFromAFourLetterString(inputString : String) -> (First : Character,Second : Character, Third : Character, Fourth : Character)
{
return (inputString[advance(inputString.startIndex, 0)],inputString[advance(inputString.startIndex, 1)],inputString[advance(inputString.startIndex, 2)],inputString[advance(inputString.startIndex, 3)])
}
func checkingWords(userEnteredWord : String)
{
var tupleFourLetters = self.returnCharactersFromAFourLetterString(userEnteredWord)
var firstLetter = String(tupleFourLetters.First)
var secondLetter = String(tupleFourLetters.Second)
var thirdLetter = String(tupleFourLetters.Third)
var fourthLetter = String(tupleFourLetters.Fourth)
var mainArrayOfWords : [String] = [] // This array contains around 0.2 million words
var userEnteredTheseWords : [String] = [] // This array contains less than 10 elements
// Check for FirstLetter
for index in 0..<array.count // Array of Letters as Strings , count = 200
{
var input = array[index]
var firstWord = "\(input)\(secondLetter)\(thirdLetter)\(fourthLetter)"
var secondWord = "\(firstLetter)\(input)\(thirdLetter)\(fourthLetter)"
var thirdWord = "\(firstLetter)\(secondLetter)\(input)\(fourthLetter)"
var fourthWord = "\(firstLetter)\(secondLetter)\(thirdLetter)\(input)"
if !contains(userEnteredTheseWords, firstWord) && !contains(userEnteredTheseWords, secondWord) && !contains(userEnteredTheseWords, thirdWord) && !contains(userEnteredTheseWords, fourthWord)
{
if contains(mainArrayOfWords, firstWord )
{
self.delegate?.wordMatchedFromDictionary(firstWord)
return
}
else if contains(mainArrayOfWords, secondWord)
{
self.delegate?.wordMatchedFromDictionary(secondWord)
return
}
else if contains(mainArrayOfWords, thirdWord)
{
self.delegate?.wordMatchedFromDictionary(thirdWord)
return
}
else if contains(mainArrayOfWords, fourthWord)
{
self.delegate?.wordMatchedFromDictionary(fourthWord)
return
}
}
if index == array.count - 1
{
self.delegate?.wordMatchedFromDictionary("NoWord")
}
}
}
Input of this function is a four letter word, Inside this function i am changing each letter by looping through that 200 letters, and checking in the mainArray that, whether any of these changed words exists in mainArray. If exists, then return me that word, otherwise just return NoWord. So totally, we can see that we are checking that contains(mainArray, word) thing around 800 times, i think this is the line which consumes more time, cause mainArray contains 0.2 million words.
Use dictionaries to look up things.
When you measure times, especially with Swift code, measure a release build, not a debug build. On the other hand, measure on the slowest device capable of running your code.

Resources