Skip to content

Instantly share code, notes, and snippets.

@dautermann
Last active July 26, 2024 19:26
Show Gist options
  • Save dautermann/059fd340622b62feeb0241813f900142 to your computer and use it in GitHub Desktop.
Save dautermann/059fd340622b62feeb0241813f900142 to your computer and use it in GitHub Desktop.
Caesar Cipher Message Match
// Question:
//Given a list of strings, group together strings that decode to the same message, using Caesar cipher encryption.
//
//Ex:
//input: ['abc', 'bcd', 'bde', 'dfg', 'hjk']
//output: [['abc', 'bcd'], ['bde', 'dfg', 'hjk']]
//
//A Caesar cipher is an encryption algorithm where rotation is applied to every character in the message. Shift value can be any number, but must be consistent across the message
//
//Algorithm:
//
//a rot(1) b
//a rot(2) c
//a rot(3) d
//
// 1 3 19 2
//'abc' <==> 'bcd' <==> 'efg' <==> 'xyz' <==> 'zab'
// Answer
import Foundation
// from https://stackoverflow.com/questions/24092884/get-nth-character-of-a-string-in-swift
extension String {
subscript(idx: Int) -> String {
String(self[index(startIndex, offsetBy: idx)])
}
}
// https://www.dotnetperls.com/caesar-swift
func caesar(value: String, shift: Int) -> String {
// Empty character array.
var result = [Character]()
// Loop over utf8 values.
for u in value.utf8 {
// Apply shift to UInt8.
let s = Int(u) + shift
// See if value exceeds Z.
// ... The Z is 26 past "A" which is 97.
// ... If greater than "Z," shift backwards 26.
// ... If less than "A," shift forward 26.
if s > 97 + 25 {
result.append(Character(UnicodeScalar(s - 26)!))
} else if s < 97 {
result.append(Character(UnicodeScalar(s + 26)!))
} else {
result.append(Character(UnicodeScalar(s)!))
}
}
// Return String from array.
return String(result)
}
// from https://stackoverflow.com/questions/26728477/how-can-i-combine-two-dictionary-instances-in-swift
extension Dictionary {
static func +=(lhs: inout Self, rhs: Self) {
lhs.merge(rhs) { _ , new in new }
}
static func +=<S: Sequence>(lhs: inout Self, rhs: S) where S.Element == (Key, Value) {
lhs.merge(rhs) { _ , new in new }
}
}
// https://stackoverflow.com/questions/29835242/whats-the-simplest-way-to-convert-from-a-single-character-string-to-an-ascii-va
extension StringProtocol {
var asciiValues: [UInt8] { compactMap(\.asciiValue) }
}
func findMatchesWithinThis(array: [String]) -> [String:[String]]? {
// probably won't happen but just in case
if (array.count == 0) {
return nil
}
var matchesDictionary: [String:[String]] = [array[0]:[array[0]]]
array.enumerated().forEach { (index, eachWordA) in
// Swift.print("checking word \(eachWordA)")
if index > 0 {
var foundMatch = false
for eachMatch in matchesDictionary.keys {
let aChar = eachWordA[0].asciiValues[0]
let mChar = eachMatch[0].asciiValues[0]
// Swift.print("aChar is \(aChar) \(eachWordA[0]) & mChar is \(mChar) \(eachMatch[0])")
var diff: Int = 0
if (aChar > mChar) {
diff = Int(aChar - mChar) * -1
} else {
diff = Int(mChar - aChar)
}
// Swift.print("checking \(eachWordA) is \(diff)")
// Swift.print("diff between \(eachWordA[0]) from \(eachWordA) & \(eachMatch[0]) from \(eachMatch) is \(diff)")
let rotatedWord = caesar(value: eachWordA, shift: diff)
if rotatedWord == eachMatch {
// Swift.print("rotated word is \(rotatedWord) \(rotatedWord.count) matches \(eachMatch) , appending \(eachWordA)")
matchesDictionary[eachMatch]?.append(eachWordA)
foundMatch = true
}
}
if foundMatch == false {
// Swift.print("creating new entry for \(eachWordA)")
matchesDictionary[eachWordA] = [eachWordA]
}
}
}
// Swift.print("matchesDictionary is \(matchesDictionary)")
return matchesDictionary
}
func findStringsThatDecodeToSameMessage(_ param: String) -> String {
// split the input string into a list of strings
let arrayOfStrings = param.components(separatedBy: " ")
// come up with dictionary where key is length,
// value is an array of strings matching that length
var dictOfStrings: [Int:[String]] = [Int:[String]]()
for eachString in arrayOfStrings {
let length = eachString.count
if let _ = dictOfStrings[length] {
dictOfStrings[length]?.append(eachString)
} else {
dictOfStrings[length] = [eachString]
}
}
//[abc] = [abc, bcd]
// abc, bcd : (b-a = 1) : bcd => -1 = abc
var totalResults: [String:[String]] = [String:[String]]()
for eachArray in dictOfStrings.values {
// Swift.print("checking \(eachArray); eachArray count is \(eachArray.count)")
if let results = findMatchesWithinThis(array: eachArray) {
totalResults += results
}
}
var outputString: String = ""
// then finish by taking those matches and appending them to an output string
for eachValue in totalResults.values {
outputString.append("[")
outputString.append(contentsOf: eachValue.joined(separator: " "))
outputString.append("],")
}
return outputString
}
Swift.print(findStringsThatDecodeToSameMessage("abc bcd bde dfg hjk"))
Swift.print(findStringsThatDecodeToSameMessage("xyz zab bde dfg hjk"))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment