Edit
I've made some changes, I think this is an improvement. A quick outline of the changes:
- Made the key mapping
[Int: [String]]
instead of[Character: [String]]
. The original version ended up breaking the input into characters then turning them into strings then breaking them back out into characters. This could have just been an array of type[[String]]
but I like the fact that dictionary returns an optional when subscripted. - Removed
isPhoneKeyMappable
. The key mapping handles this by removing nils (viaflatMap
) when converting from digits to array ofString
. - Added some helper methods, renamed other.
phoneWords
has been replaced with a more generalpermute
function which simply returns all the permutations of the subarray items.
Here is the updated code (not including the words
list which hasn't changed):
/*:
This solution involves using a map of `Int` 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. This could be done as an array since the key is an `Int`, but using a map gives us added safety by returning optionals when subscripting.
*/
let keyMap: [Int: [String]] = [
0: ["0"],
1: ["1"],
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 permutations
1. handle empty string
2. termination condition
3. recursively generate permutations on smaller inputs
4. combine prefixes with each result from recursive permutation
*/
func permute(parts: [[String]]) -> [String] {
guard let prefixes = parts.first else { return [] } // 1
if parts.count == 1 { return prefixes } // 2
let sx = permute(Array(parts.dropFirst(1))) // 3
return prefixes.flatMap { p in sx.map { s in p + s } }
}
/*:
Some helper functions to make things more readable later
*/
func hasMoreThanOne<T: CollectionType>(c: T) -> Bool {
return c.count > 1
}
func filter<T: CollectionType>(pred: T.Generator.Element -> Bool)(c: T) -> [T.Generator.Element] {
return c.filter(pred)
}
func flatMap<T: CollectionType, V>(transform: T.Generator.Element -> V?)(c: T) -> [V] {
return c.flatMap(transform)
}
func transform<T: Hashable, V>(dict: [T:V])(element: T) -> V? {
return dict[element]
}
infix operator |> { precedence 50 associativity left }
public func |> <T,U>(lhs: T, rhs: T -> U) -> U {
return rhs(lhs)
}
extension Int {
init?(c: Character) {
guard let i = Int(String(c)) else { return nil }
self = i
}
}
extension CollectionType where Generator.Element : Equatable {
public func split(separators: Self.Generator.Element...) -> [Self.SubSequence] {
return self.split(isSeparator: separators.contains)
}
}
/*:
Generate phone words for the phone number `1-800-FLOWERS`
1. break the string down into an array of `Character`
2. convert each `Character` into an `Int`
3. split into subarrays of `Int`
4. replace each digit with a list of characters
5. filter out single-digit subarrays
6. generate permutations
7. filter out non-engligh words
*/
let mnemonics = "1-800-356-9377"
.characters // 1
.flatMap(Int.init) // 2
.split(1, 0) // 3
.map(keyMap |> transform |> flatMap) // 4
.filter(hasMoreThanOne) // 5
.map(permute) // 6
.map(isWord |> filter) // 7
print(mnemonics) // [["flowers"]]