Toggle case of a string in Swift What is benefit of using uppercased() / lowercased() over the following approach ? (Other than making it more readable)

Any comments on perf ?

Can this be further improved ?

Example : for input : ABcDe, Expected Output is : abCdE

var solve = {(str: String) -&gt; String in
    var arr = [Character]()
    for char in str.unicodeScalars {
        char.value &gt; 96 ? arr.append(Character(UnicodeScalar(char.value - 32)!)) : arr.append(Character(UnicodeScalar(char.value + 32)!))
    }
    return String(arr)
}

solve("Nipun")
Compared to a solution using the upper/lowerCased() string methods,
your approach has some disadvantages:


It does not work for many letters in non-english alphabets:

print(solve("ćœßπĆŒΠ"))  // çĳ¿ΠæĲ΀

It transforms non-letters as well, to some unexpected output:

print(solve("a b,c")) // A@BLC





Even if the code is only needed for ASCII letters ("A" ... "Z", "a" ... "z"),
it can still be improved.

Defining a global function instead of a closure variable increases the
legibility. You should also use a better function name, solve() is pretty
non-descriptive:

func toggleCase(_ str: String) -&gt; String {
    // ...
}


An alternative would be an extension method:

extension String {
    func caseToggled() -&gt; String {
        // ...
    }
}


which resembles the existing upper/lowerCased() methods.
A disadvantage is the possibility of name conflicts if some imported
framework happens to define the same extension method.

The use of "magic numbers" – like 32 and 96 in your code – should be
avoided. At least use a constant with an explaining comment:

let delta = 32 // Difference between upper case and lower case Unicode scalar values of ASCII letters.


Better, compute it from the actual values, which makes it self-explaining:

let delta = UnicodeScalar("a")!.value - UnicodeScalar("A")!.value


A switch-case statement allows to transform only the ASCII letters,
with a well-defined behavior for other characters.

Finally, instead of creating an intermediate [Character] array,
you can append to the unicodeScalar view of the result string
directly.

Putting it together, the function could look like this:

func toggleCase(_ str: String) -&gt; String {
    let delta = UnicodeScalar("a")!.value - UnicodeScalar("A")!.value
    var result = ""
    for ucs in str.unicodeScalars {
        switch ucs {
        case "A"..."Z":
            result.unicodeScalars.append(UnicodeScalar(ucs.value + delta)!)
        case "a"..."z":
            result.unicodeScalars.append(UnicodeScalar(ucs.value - delta)!)
        default:
            result.unicodeScalars.append(ucs) // Leave unchanged
            // Alternatively:
            // break to ignore other characters
        }
    }
    return result
}


Example:

print(toggleCase("AB c De")) // ab C dE


If you have to toggle the case of arbitrary letters (from any language) then
the upper/lowerCased() string methods must be used, as there is no
simple "arithmetic operation" which does this transformation.



If performance is the first priority then you should work on the UTF-16 view
of the string, because that is what String stores internally. (However, that
is an implementation detail and might change in the future.)

func toggleCase1(_ str: String) -&gt; String {
    var utf16Result: [UInt16] = []
    utf16Result.reserveCapacity(str.utf16.count)
    let upperCaseA = UInt16(65)
    let upperCaseZ = UInt16(90)
    let lowerCaseA = UInt16(97)
    let lowerCaseZ = UInt16(122)
    let delta = lowerCaseA - upperCaseA

    for u in str.utf16 {
        switch u {
        case upperCaseA...upperCaseZ:
            utf16Result.append(u + delta)
        case lowerCaseA...lowerCaseZ:
            utf16Result.append(u - delta)
        default:
            utf16Result.append(u)
        }
    }
    return String(utf16CodeUnits: utf16Result, count: utf16Result.count)
}


Note how reserveCapacity() is used to avoid array reallocations.

Performance comparison: The test was done on a MacBook (Retina, 12-inch, Early 2016, 1.2 GHz Intel Core m5 processor),
with the program compiled in Release configuration:

let str = String(repeating: "abcdefghijklmnopqrstuvwsyzABCDEFGHIJKLMNOPQRSTUVWXYZ", count: 100_000)

do {
    let start = Date()
    let _ = solve(str)
    let end = Date()
    print("solve:      ", end.timeIntervalSince(start) * 1000)
}

do {
    let start = Date()
    let _ = toggleCase(str)
    let end = Date()
    print("toggleCase: ", end.timeIntervalSince(start) * 1000)
}

do {
    let start = Date()
    let _ = toggleCase1(str)
    let end = Date()
    print("toggleCase1:", end.timeIntervalSince(start) * 1000)
}


Result:


solve:       496.731996536255
toggleCase:  319.347023963928
toggleCase1: 189.152002334595