Created
November 24, 2015 22:42
-
-
Save cfilipov/5cab0c3dccfa3d25b540 to your computer and use it in GitHub Desktop.
Generating Phone Words in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*: | |
## 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