Generating Phone Words in Swift
/*: | |
## Phone Words | |
Generate a collection of words that can be represented by a given phone number. If a phone number contains the digits `1` or `0` then split up the phone number and find the words for each of the substrings as long as each substring has more than one digit. Non-keypad characters can be ignored. Optionally, filter out words so that only dictionary words are present in the result. | |
╔═════╦═════╦═════╗ | |
║ 1 ║ 2 ║ 3 ║ | |
║ ║ abc ║ def ║ | |
╠═════╬═════╬═════╣ | |
║ 4 ║ 5 ║ 6 ║ | |
║ ghi ║ jkl ║ mno ║ | |
╠═════╬═════╬═════╣ | |
║ 7 ║ 8 ║ 9 ║ | |
║ pqrs║ tuv ║wxyz ║ | |
╠═════╬═════╬═════╣ | |
║ * ║ 0 ║ # ║ | |
║ ║ ║ ║ | |
╚═════╩═════╩═════╝ | |
Example | |
`1-8000-356-9377` should return "FLOWERS" | |
*/ | |
/* | |
The built-in words file "/usr/share/dict/words" can also be used. But it seems to be missing plurals. | |
The "words.txt" file used below can be found at [dwyl/english-words](https://github.com/dwyl/english-words). | |
*If you are pasting this into a playground then it's best to put this part in the "Sources" directory so it doesn't get evaluated every time* | |
*/ | |
let words = Set( | |
try! String(contentsOfURL: NSBundle.mainBundle().URLForResource("words", withExtension: "txt")!) | |
.componentsSeparatedByCharactersInSet( | |
NSCharacterSet.newlineCharacterSet() | |
) | |
) | |
public func isWord(s: String) -> Bool { | |
return words.contains(s) | |
} | |
/*: | |
This solution involves using a map of `Character` to `String` arrays. `String` arrays are used instead of just strings since we have to split each string into characters and convert back to `String` for concatenation anyway. | |
*/ | |
let phoneKeyMap: [Character: [String]] = [ | |
"2": ["a","b","c"], | |
"3": ["d","e","f"], | |
"4": ["g","h","i"], | |
"5": ["j","k","l"], | |
"6": ["m","n","o"], | |
"7": ["p","q","r","s"], | |
"8": ["t","u","v"], | |
"9": ["w","x","y","z"] | |
] | |
/*: | |
Generate phone words recursively | |
1. handle empty string | |
2. handle non keypad characters | |
3. termination condition | |
4. recursively build words | |
5. combine words with all characters for cur key | |
*/ | |
func phoneWords(s: String) -> [String] { | |
guard let n = s.characters.first else { return [] } // 1 | |
guard let px = phoneKeyMap[n] else { return [] } // 2 | |
if s.characters.count == 1 { return px } // 3 | |
let sx = phoneWords(String(s.characters.dropFirst())) // 4 | |
return px.flatMap { p in sx.map { s in p + s } } // 5 | |
} | |
/*: | |
Some helper functions to make things more readable | |
*/ | |
func isPhoneKeyMappable(c: Character) -> Bool { | |
return ("2"..."9").contains(String(c)) | |
} | |
func hasMoreThanOne<T: CollectionType>(c: T) -> Bool { | |
return c.count > 1 | |
} | |
func filterWith<T: CollectionType>(pred: T.Generator.Element -> Bool)(c: T) -> [T.Generator.Element] { | |
return c.filter(pred) | |
} | |
/*: | |
Generate phone words for the phone number `1-800-FLOWERS` | |
1. split the phone number at "1" and "0" | |
2. filter out digits that are not 2-9 | |
3. filter out single-digit words | |
4. convert each collection of `Character` into a `String` | |
5. convert each string into a collection of words based on keypad | |
6. filter out non-engligh words based on a word dictionary | |
*/ | |
let mnemonics = "1-800-356-9377".characters | |
.split { ("0"..."1").contains(String($0)) } // 1 | |
.map(filterWith(isPhoneKeyMappable)) // 2 | |
.filter(hasMoreThanOne) // 3 | |
.map(String.init) // 4 | |
.map(phoneWords) // 5 | |
.map(filterWith(isWord)) // 6 | |
print(mnemonics) // [["flowers"]] | |
/*: | |
Do the reverse; convert a mnemonic phone word to its phone number. | |
*/ | |
func phoneNumber(s: String) -> String { | |
return s.lowercaseString.characters.map { | |
switch $0 { | |
case "a"..."c": return "2" | |
case "d"..."f": return "3" | |
case "g"..."i": return "4" | |
case "j"..."l": return "5" | |
case "m"..."o": return "6" | |
case "p"..."s": return "7" | |
case "t"..."v": return "8" | |
case "w"..."z": return "9" | |
default: return String($0) | |
} | |
}.reduce("", combine: +) | |
} | |
phoneNumber("1-800-FLOWERS") // "1-800-3569377" | |
// I do not endorse 1-800-FLOWERS |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment