How to make this LeetCode Swift solution cleaner or more efficient?

I am trying to learn more about Swift by doing leetcode in Swift. On the following problem, I used a recursive approach, which works, but I think it looks a little clunky:

Problem

Given a string containing digits from 2-9 inclusive, return all possible letter combinations that the number could represent. Return the answer in any order .

A mapping of digit to letters (just like on the telephone buttons) is given below. Note that 1 does not map to any letters.

Example 1:

Input: digits = "23" Output: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Example 2:

Input: digits = "" Output:

Example 3:

Input: digits = "2" Output: ["a","b","c"]

class Solution {
    func search(_ result: inout Array<String>, _ i: Int, _ string: String, _ curr: String) -> Void {
        // I would probably usually make this a property of the class, so each
        // frame doesn't have to create a new dict every time.
        let keyMap = ["2": "abc",
                      "3": "def",
                      "4": "ghi",
                      "5": "jkl",
                      "6": "mno",
                      "7": "pqrs",
                      "8": "tuv",
                      "9": "wxyz"]
        if (i == string.count) {
            result.append(curr)
            return
        }
        // the following line in particular looks very clunky, in Python
        // this would be string[i]
        let index : String = String(string[string.index(string.startIndex, offsetBy: i)])
        let possible : String = keyMap[index]!
        for char in possible {
            search(&result, i + 1, string, curr + String(char))
        }
    }
    
    func letterCombinations(_ digits: String) -> [String] {
        var result : Array<String> = []
        if (digits.count == 0) {
            return result
        }
        search(&result, 0, digits, "")
        return result
    }
}
2 Likes

You can check out this solution from the repo LeetCode-Swift by @soapyigu

class Solution {
    func letterCombinations(_ digits: String) -> [String] {
        guard digits.count > 0 else {
            return [String]()
        }
        
        var combinations = [String](), combination = ""
        let numberToStr = ["", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"]
        
        dfs(&combinations, &combination, numberToStr, digits, 0)
        
        return combinations
    }
    
    private func dfs(_ combinations: inout [String], _ combination: inout String, _ numberToStr: [String], _ digits: String, _ index: Int) {
        if combination.count == digits.count {
            combinations.append(combination)
            return
        }
        
        let currentStr = fetchCurrentStr(from: digits, at: index, numberToStr)
        
        for char in currentStr {
            combination.append(char)
            dfs(&combinations, &combination, numberToStr, digits, index + 1)
            combination.removeLast()
        }
    }
        
    private func fetchCurrentStr(from digits: String, at index: Int, _ numberToStr: [String]) -> String {
        guard index >= 0 && index < digits.count else {
            fatalError("Invalid index")
        }
        
        let currentDigitChar = digits[digits.index(digits.startIndex, offsetBy: index)]
        
        guard let currentDigit = Int(String(currentDigitChar)), currentDigit >= 0, currentDigit < numberToStr.count else {
            fatalError("Invalid digits")
        }
        
        return numberToStr[currentDigit]
    }
}