Search code examples
xcodeswift3swift-playground

Bad instruction error Swift 3 and XCode 8


I've been learning Swift 3 syntax lately and I thought that a good first project would be a Vigenère Cipher. So I've begun to create a script for it in Playground.

The issue is that I keep getting an error when I call the method and I know what the mistake is, it has to do with how I am calling my Dictionary value and the fact that I am unwrapping it, But I don't know what else to do about it. Any ideas?

import Foundation


let alphabet: [String: Int] = ["a": 0, "b": 1, "c": 2, "d": 3, "e": 4, "f": 5, "g": 6, "h": 7, "i": 8,
                           "j": 9, "k": 10, "l": 11, "m": 12, "n": 13, "o": 14, "p": 15, "q": 16,
                           "r": 17, "s": 18,"t": 19, "u": 20, "v": 21, "w": 22, "x": 23, "y": 24, "z": 25]

let alphabet2: [Int: String] = [0: "a", 1: "b", 2: "c", 3: "d", 4: "e", 5: "f", 6: "g", 7: "h", 8: "i",
                            9: "j", 10: "k", 11: "l", 12: "m", 13: "n", 14: "o", 15: "p", 16: "q",
                            17: "r", 18: "s", 19: "t", 20: "u", 21: "v", 22: "w", 23: "x", 24: "y", 25: "z"]


var mess = "I once saw a big mosquito"
var key = "pass"


func cipher(message: String, key: String) -> String{
    var code: [Int] = [] // will hold the encripted code

    // removes whietspace from message and key
    let trimmedMessage = message.trimmingCharacters(in: NSCharacterSet.whitespaces)
    let trimmedKey = key.trimmingCharacters(in: NSCharacterSet.whitespaces)

    // Sets the key the same size as the message
    let paddedTrimmedKey = trimmedKey.padding(toLength: message.characters.count, withPad: trimmedKey, startingAt: 0)

    // separates the message and key into individual characters
    let charTrimmedMessage = Array(trimmedMessage.characters)
    let charPaddedTrimmedKey = Array(paddedTrimmedKey.characters)


    // Compare the values in the key to the message and scrambles the message.
    var i = 0
    for chr in charTrimmedMessage{
        code.append((alphabet[String(chr)]! + alphabet[String(charPaddedTrimmedKey[i])]!) % 26) // <- I think the error comes from this line. Maybe the exclamation marks?
        i += 1
    }


    var cyphr: String = "" // this will hold the return String

    // I think I could have used some sort of "join" function here.
    for number in code{
        cyphr = cyphr + alphabet2[number + 1]!
    }

    return cyphr
}

cipher(message: mess, key: key) // <--- this returns an error, no clue why. The code works and compiles great.

I get this error:

Error

If you could let me know any pointer on how to improve my code to avoid things like this even better.


Solution

  • Your alphabet dictionary does not have lookup values for the letters "I", " " which are contained in your plain text. The cipher function fails because of this (since it is force unwrapping an optional which is nil)

    If you initialize the mess variable, you will get the cipher to work

    var mess = "ioncesawabigmosquito"
    cipher(...) -> "ypgvuttpqcbzcpljkjmh"
    

    Either

    • update the alphabet lookup dictionary. E.g. add ' ' as a valid input, update alphabet2 to provide reverse lookup, and change the mod factor from 26 to 27 (to account for new ' ' character)

    OR

    • validate your input beforehand. (trim spaces, replace spaces, convert uppercase to lowercase / strip characters not in valid range)

    To trim your input to only include valid letters in the alphabet, you could try:

    let validSet = CharacterSet.init(charactersIn: alphabet.keys.joined())
    var messTrimmed = mess.trimmingCharacters(in: validSet.inverted)
    

    Note that doing this mean losing information from the original message


    Another bug:

    The line cyphr = cyphr + alphabet2[number+1]! should be cyphr = cyphr + alphabet2[number]!.

    It's not correct to add 1 to number since the values in the code array are computed mod 26, and the max key value in alphabet is 25. It will cause an exception when force unwrapping to a non-existent key.

    E.g. try cipher(message: "ypgvuttpqcbzcpljkjmh", key: "pass") to make it fail.


    Footnote

    As a complete aside, here's a variant of the cipher function (I have not sanitized the inputs; just showing how the code could be written in a functional way)

    func cipher2(message: String, key: String) -> String {
        return
            message
            .characters
            .enumerated()
            .map { ($0, String($1)) }
            .map {
                (
                        alphabet[$1]!
                    +   alphabet[String(key[key.index(key.startIndex, offsetBy: ($0 % key.characters.count))])]!
                ) % 26
            }
            .map { alphabet2[$0]! }
            .joined()
    }