Caesar encryption and decryption For a small project, I had to implement Caesar encryption in Swift(4). I'm mainly looking for performance optimizations.

This whole code should be copy and pasteable in a playground and is functioning as expected as far as I can see.

import Foundation

let caesarNumber:Int = 7;

let alphabet_array:[Character] = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"]

func encode(inputString: String) -&gt; String {
   var resultString:String = ""

   for char in inputString {
       for i in 0...(alphabet_array.count-1) {
           if (char == alphabet_array[i]) {
               if (i + caesarNumber &lt; alphabet_array.count-1) {
                   resultString.append(alphabet_array[i+caesarNumber])
               }
               else {
                   let subscripture:Int = (i+caesarNumber)%alphabet_array.count
                   resultString.append(alphabet_array[subscripture])
               }
           }
       }
   }
   return resultString
}

func decode(inputString: String) -&gt; String {
   var resultString:String = ""

   for char in inputString {
       for i in 0...(alphabet_array.count-1) {
           if (char == alphabet_array[i]) {
               if (i - caesarNumber &gt;= 0) {
                   resultString.append(alphabet_array[i-caesarNumber])
               }
               else {                         
                   resultString.append(
                      alphabet_array[alphabet_array.count+(i-caesarNumber)])
               }
           }
       }
   }
   return resultString
}

   let encodedString = encode(inputString: "zacharias")
   let decodeString = decode(inputString: encodedString);
Naming

According to the Swift API Design Guidelines, variable names are lower camel case (not alphabet_array),
and types should not be part of the variable name (not caesarNumber, inputString, resultString).

It is also clear that the first (and only) argument of encode() and decode() 
is the input string, here we can omit the argument label:

func encode(_ input: String) -&gt; String


which is then called as

let encodedString = encode("zacharias")


Improving the API

The current API has two drawbacks:


It uses the global variable caesarNumber to pass information to the 
functions.
The function names (“encode”, “decode”) are too common, they to not tell
what the function actually does.


Here are two alternative suggestions:


Make the function names more specific, and pass the shift amount as an
additional argument.


You can also define a default parameter value for the shift amount.

func caesarEncode(_ input: String, shiftBy: Int = 7) -&gt; String 
func caesarDecode(_ input: String, shiftBy: Int = 7) -&gt; String 



Define a cipher type with parameters, and encode/decode methods.


That would allow to substitute the Caesar cipher by other methods easily.

struct CaesarCipher {
    let shiftAmount: Int

    init(shiftAmount: Int = 7) {
        self.shiftAmount = shiftAmount
    }

    func encode(_ input: String) -&gt; String { ... }
    func decode(_ input: String) -&gt; String { ... }
}


Example usage:

let rot13 = CaesarCipher(shiftAmount: 13)
let encrypted = rot13.encode("Hello World")


I'll stick with the first API for the remainder of this review.

Simplify the code

The alphabet can be initialized simply with

let alphabet: [Character] = Array("abcdefghijklmnopqrstuvwxyz")


because a String is a sequence of its Characters. Locating a character
in the array can be done with

if let idx = alphabet.index(of: char) { ... }


instead of a loop. The test for

 if (i + caesarNumber &lt; alphabet_array.count-1)


is not needed, because the “else case” (with the modulo arithmetic) works actually in both cases, with or without wrap-around.

Summarizing the above suggestions so far, we have for the encoding method:

func caesarEncode(_ input: String, shiftBy: Int = 7) -&gt; String {
    var result: String = ""
    for char in input {
        if let idx = alphabet.index(of: char) {
            let newIdx = (idx + shiftBy) % alphabet.count
            result.append(alphabet[newIdx])
        }
    }
    return result
}


Decoding is the same as encoding, only with a shift in the opposite direction.
If we modify the encoding method slightly to work with negative shift amounts then it can be used for the decoding as well:

func caesarEncode(_ input: String, shiftBy: Int = 7) -&gt; String {
    var result: String = ""
    for char in input {
        if let idx = alphabet.index(of: char) {
            var newIdx = (idx + shiftBy) % alphabet.count
            if newIdx &lt; 0 { newIdx += alphabet.count }
            result.append(alphabet[newIdx])
        }
    }
    return result
}

func caesarDecode(_ input: String, shiftBy: Int = 7) -&gt; String {
    return caesarEncode(input, shiftBy: -shiftBy)
}


Improve the performance

For optimal performance on really long strings you can operate on the
UTF-16 view instead, that allows to determine the offset within the alphabet with pure integer arithmetic instead of the array lookup.

Enumerating the UTF-16 view also seems to be faster than enumerating
the characters (which represent extended grapheme clusters).

A possible implementation could look like this:

func caesarEncode(_ input: String, shiftBy: Int = 7) -&gt; String {
    let letterA = Int("a".utf16.first!)
    let letterZ = Int("z".utf16.first!)
    let letterCount = letterZ - letterA + 1

    var result = [UInt16]()
    result.reserveCapacity(input.utf16.count)

    for char in input.utf16 {
        let value = Int(char)
        switch value {
        case letterA...letterZ:
            let offset = value - letterA
            var newOffset = (offset + shiftBy) % letterCount
            if newOffset &lt; 0 { newOffset += letterCount }
            result.append(UInt16(letterA + newOffset))
        default:
            break
        }
    }

    return String(utf16CodeUnits: &amp;result, count: result.count)
}


On my computer this was faster than the previous method (3 milliseconds
instead of 11 milliseconds for a string with 100,000 characters).

For shorter strings that probably won't matter, and you can choose what you
feel more familiar with.