Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement a ROT13 function in Swift?

I would like to create a function that receives a String and returns a String, and that replaces a letter with the letter 13 letters after it in the alphabet (ROT13). I found a lot of examples, unfortunately I was not able to make it work no one because of various errors. For example this one:

var key = [String:String]() // EDITED
let uppercase = Array(arrayLiteral: "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
let lowercase = Array(arrayLiteral: "abcdefghijklmnopqrstuvwxyz")
for i in 0 ..< 26 {
    key[uppercase[i]] = uppercase[(i + 13) % 26]
    key[lowercase[i]] = lowercase[(i + 13) % 26]
}

func rot13(s: String) -> String {
    return String(map(s, { key[$0] ?? $0 }))
}
like image 708
Cue Avatar asked Dec 04 '22 00:12

Cue


2 Answers

Actually your initial approach of mapping Characters was good:

var key = [Character: Character]()

But the two arrays must the be arrays of Characters:

let uppercase = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters)
let lowercase = Array("abcdefghijklmnopqrstuvwxyz".characters)

(Remark: You (almost) never want to call a xxxLiteral: initializer explicitly. Doing so is (almost) always hiding the actual problem.)

Now your code to fill the dictionary works:

for i in 0 ..< 26 {
    key[uppercase[i]] = uppercase[(i + 13) % 26]
    key[lowercase[i]] = lowercase[(i + 13) % 26]
}

And transforming a string can be done as

//                       map
// String --> Characters ---> Characters -> String

func rot13(s: String) -> String {
    return String(s.characters.map { key[$0] ?? $0 })
}
like image 144
Martin R Avatar answered Dec 16 '22 11:12

Martin R


This is an alternate version of @AMomchilov's rot13 which uses a switch and less math and eliminates magic numbers:

func rot13(_ unicodeScalar: UnicodeScalar) -> Character {
    var result = unicodeScalar.value
    
    switch unicodeScalar {
    case "A"..."M", "a"..."m":
        result += 13
    case "N"..."Z", "n"..."z":
        result -= 13
    default:
        break
    }
    
    return Character(UnicodeScalar(result)!)
}

func rot13(_ input: String) -> String {
    return String(input.unicodeScalars.map(rot13))
}

print(rot13("Uryyb, jbeyq!")) // "Hello, world!"

Generalization to rotN

I've taken the rot13 functions above and generalized them to rotN by having them take an array of ClosedRange<UnicodeScalar>. This allows you to implement rot13, rot47, rot5, and the combination of rot13 and rot5 in a very straightforward manner.

func rotN(_ unicodeScalar: UnicodeScalar, intervals:[ClosedRange<UnicodeScalar>]) -> Character {
    var result = unicodeScalar.value

    for interval in intervals {
        let half = (interval.upperBound.value - interval.lowerBound.value + 1) / 2
        let halfway = UnicodeScalar(interval.lowerBound.value + half)!

        switch unicodeScalar {
        case interval.lowerBound..<halfway:
            result += half
        case halfway...interval.upperBound:
            result -= half
        default:
            break
        }
    }

    return Character(UnicodeScalar(result)!)
}

func rotN(_ input: String, intervals:[ClosedRange<UnicodeScalar>]) -> String {
    return String(input.unicodeScalars.map {rotN($0, intervals: intervals)})
}

func rot13(_ input: String) -> String {
    return rotN(input, intervals:["A"..."Z", "a"..."z"])
}

func rot47(_ input: String) -> String {
    return rotN(input, intervals:["!"..."~"])
}

func rot5(_ input: String) -> String {
    return rotN(input, intervals:["0"..."9"])
}

func rot13and5(_ input: String) -> String {
    return rotN(input, intervals:["A"..."Z", "a"..."z", "0"..."9"])
}

print(rot13("Uryyb, jbeyq!"))        // "Hello, world!"
print(rot47("%96 BF:4< 3C@H? 7@I"))  // "The quick brown fox"
print(rot5("6 + 7 = 8"))             // "1 + 2 = 3"
print(rot13and5("Whyl 9, 6221"))     // "July 4, 1776"

Here is a version of rotN based upon @AMomchilov's original rot13:

func rotN(_ unicodeScalar: UnicodeScalar, intervals:[ClosedRange<UnicodeScalar>]) -> UnicodeScalar {
    var result = unicodeScalar.value
    
    for interval in intervals {
        let start = interval.lowerBound.value
        let length = interval.upperBound.value - start + 1
        
        if interval ~= unicodeScalar {
            result = (result + length/2 - start) % length + start
        }
    }
    
    return UnicodeScalar(result)!
}

func rotN(_ input: String, intervals:[ClosedRange<UnicodeScalar>]) -> String {
    return String(input.unicodeScalars.map {Character(rotN($0, intervals:intervals))})
}
like image 39
vacawama Avatar answered Dec 16 '22 10:12

vacawama