Julius Caesar protected his confidential information by encrypting it in a cipher. Caesar's cipher rotated every letter in a string by a fixed number, K
, making it unreadable by his enemies. Given a string,S
, and a number, K
, encrypt S
.
Note: The cipher only encrypts letters; symbols, such as -
, remain unencrypted.
func cipher(messageToCipher: String, k: Int)-> String{
var eMessage = ""
let arr = messageToCipher.characters.map { String(0ドル) }
for ch in arr {
for code in String(ch).utf8 {
if (65<=code && code<=90) || (97<=code && code<=122) {
if k > 26{
let value = k % 26 == 0 ? k / 26 : k % 26
var pCode = ((Int(code)) + (value))
if (pCode > 122 && (97<=code && code<=122)) || (pCode > 90 && (65<=code && code<=90)) {
pCode = pCode - 26
}
let s = String(UnicodeScalar(UInt8(pCode)))
eMessage = eMessage + s
}else
{
var pCode = Int(code) + k
if (pCode > 122 && (97<=code && code<=122)) || (pCode > 90 && (65<=code && code<=90)) {
pCode = pCode - 26
}
let s = String(UnicodeScalar(UInt8(pCode)))
eMessage = eMessage + s
}
}
else{
eMessage = eMessage + ch
}
}
}
return eMessage
}
print(cipher(messageToCipher: "abc", k: 2) )
I solved this way and it was passed all test cases.
More information on this problem: Caesar Cipher
1 Answer 1
Enumerating UTF-8 characters: Your code uses two nested loops to encrypt letters (based on the UTF-8 code):
var eMessage = ""
let arr = messageToCipher.characters.map { String(0ドル) }
for ch in arr {
for code in String(ch).utf8 {
if (65<=code && code<=90) || (97<=code && code<=122) {
// ... translate `code` and append to `eMessage` ...
}
else{
eMessage = eMessage + ch
}
}
}
This is unnecessary complicated and has an unwanted side effect if the message contains non-ASCII characters (which are encoded as 2 or more UTF-8 code units):
print(cipher(messageToCipher: "ä € 😀 🇧🇩", k: 2) )
// ää €€€ 😀😀😀😀 🇧🇩🇧🇩🇧🇩🇧🇩🇧🇩🇧🇩🇧🇩🇧🇩
This could be solved by adding a break
in the else-case. The better solution is to
enumerate the UTF-8 code units directly:
var encoded: [UInt8] = []
for code in messageToCipher.utf8 {
if (65<=code && code<=90) || (97<=code && code<=122) {
// ... translate `code` and append to `encoded` ...
}
else{
encoded.append(code)
}
}
return String(bytes: encoded, encoding: .utf8)!
Code duplication: There is some duplicate code in
if k > 26{
let value = k % 26 == 0 ? k / 26 : k % 26
var pCode = ((Int(code)) + (value))
if (pCode > 122 && (97<=code && code<=122)) || (pCode > 90 && (65<=code && code<=90)) {
pCode = pCode - 26
}
let s = String(UnicodeScalar(UInt8(pCode)))
eMessage = eMessage + s
}else
{
var pCode = Int(code) + k
if (pCode > 122 && (97<=code && code<=122)) || (pCode > 90 && (65<=code && code<=90)) {
pCode = pCode - 26
}
let s = String(UnicodeScalar(UInt8(pCode)))
eMessage = eMessage + s
}
which is not necessary. The if-part works for the case k <= 26
as well.
Exceptional key values: The special handling of the case k % 26 == 0
causes
unexpected results:
print(cipher(messageToCipher: "abc xyz ABC XYZ", k: 26) ) // abc xyz ABC XYZ
print(cipher(messageToCipher: "abc xyz ABC XYZ", k: 52) ) // cde zab CDE ZAB
and is not needed. Negative key values are not handled at all:
print(cipher(messageToCipher: "abc xyz ABC XYZ", k: -2) ) // _`a vwx ?@A VWX
print(cipher(messageToCipher: "abc xyz ABC XYZ", k: -200) )
// Fatal error: Negative value is not representable
Both issues can be solved by computing the remainder of k
modulo 26, as a number
in the range 0...25:
var value = k % 26
if value < 0 { value += 26 }
In addition, this computation can be done once, before entering the loop.
Other issues:
- I would use some different argument/variable names, e.g.
message
instead ofmessageToCipher
(it is clear from the context and the function name that this string should be encrypted), orkey
instead ofk
. There are unnecessary parentheses, e.g. in
var pCode = ((Int(code)) + (value))
The usage of white space is not consistent, e.g. in
if (pCode > 122 && (97<=code && code<=122)) || (pCode > 90 && (65<=code && code<=90)) {
Summarizing the suggestions so far, we have
func cipher(message: String, key: Int) -> String {
var offset = key % 26
if offset < 0 { offset += 26 }
var encoded: [UInt8] = []
for code in message.utf8 {
if (65 <= code && code <= 90) || (97 <= code && code <= 122) {
var pCode = Int(code) + offset
if (pCode > 122 && (97 <= code && code <= 122)) || (pCode > 90 && (65 <= code && code <= 90)) {
pCode -= 26
}
encoded.append(UInt8(pCode))
} else {
encoded.append(code)
}
}
return String(bytes: encoded, encoding: .utf8)!
}
Make it functional: If the encryption of a single character is moved to a separate
function then one can apply this to the UTF-8 view with map()
:
func cipher(message: String, key: Int) -> String {
var offset = key % 26
if offset < 0 { offset += 26 }
func utf8encrypt(code: UInt8) -> UInt8 {
// ... compute and return encrypted character ...
}
return String(bytes: message.utf8.map(utf8encrypt), encoding: .utf8)!
}
Further suggestions:
Use a switch-statement with range patterns instead of the if-conditions:
switch code { case 65...90: // ... case 97...122: // ... default: // ... }
Use constants instead of the literal numbers, e.g.
let letter_A = 65 let letter_Z = 90 let letter_a = 97 let letter_z = 122
so that a future reader of your code does not have to guess what the numbers stand for.
-
1\$\begingroup\$ @NazmulHasan: You are welcome! – (You might also be interested in codereview.stackexchange.com/questions/172017/…, where that "encryption" is done based on Unicode scalars instead of UTF-8 code units.) \$\endgroup\$Martin R– Martin R2017年12月04日 10:32:59 +00:00Commented Dec 4, 2017 at 10:32
Explore related questions
See similar questions with these tags.