Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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
You can’t perform that action at this time.