Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle large strings in swift?

I've this code and its taking a lot of time to execute in swift? Each iteration takes 1 second to execute, Why?

CPU Percentage while executing that loop is 97-98% and energy impact is High

Here is the Code

     var braces:Int = 1;
     var i:Int = startIndex;
     let jsFileChars = Array(javascriptFile);
      while(i < javascriptFile.count){   //count:1240265        
         if (braces == 0) {
              break;
         }                            
        if (jsFileChars[i] == "{"){     
             braces = braces+1;
         }else if (jsFileChars[i] == "}"){
              braces = braces-1;
         }
             i = i+1;
   }

This loop is iterated on a very slow pace, why?

like image 575
Group Avatar asked Mar 01 '18 12:03

Group


2 Answers

The loop is slow because determining the count of a Swift string is a O(N) operation, where N is the number of characters in the string. See also Counting Characters in “The Swift Programming Language”:

NOTE

Extended grapheme clusters can be composed of multiple Unicode scalars. This means that different characters—and different representations of the same character—can require different amounts of memory to store. Because of this, characters in Swift don’t each take up the same amount of memory within a string’s representation. As a result, the number of characters in a string can’t be calculated without iterating through the string to determine its extended grapheme cluster boundaries. ...

Replacing javascriptFile.count by jsFileChars.count should already improve the performance, because the length of an array is determined in constant time.

Even better iterate over the characters directly, without creating an array at all:

var braces = 1
for char in javascriptFile {
    if char == "{" {
        braces += 1
    } else if char == "}" {
        braces -= 1
    }
}

Iterating over the UTF-16 view is even faster, because that is what Swift strings (currently) use as internal storage:

let openingBrace = "{".utf16.first!
let closingBrace = "}".utf16.first!

var braces = 1
for char in javascriptFile.utf16 {
    if char == openingBrace {
        braces += 1
    } else if char == closingBrace {
        braces -= 1
    }
}
like image 82
Martin R Avatar answered Oct 30 '22 00:10

Martin R


When you're thinking of iterating over a collection in Swift (and a String is a collection of characters), it's sometimes faster to use reduce() instead. You can implement your brace counter using reduce() like this:

let braces = javascriptFile.reduce(0, { count, char in
    switch char {
    case "{": return count + 1
    case "}": return count - 1
    default: return count
    }
})

I don't know if that'll actually be faster than using a for loop in your case, but might be worth a try. If nothing else, the intent is very clear when written this way.

like image 24
Caleb Avatar answered Oct 30 '22 00:10

Caleb