Search code examples
swiftstringcapitalization

How can I find all capitalization combinations of a String in Swift?


Problem

I'm trying to find all of the possible combinations of capitalization for a String in Swift. For example, given the String "abc", I would want my method to return an Array of Strings, like this: ["Abc", "aBc", "abC", "ABc", "abc", "ABC", "aBC", "AbC"]. The formula for the possible number of capitalization combinations is like this:

2i

where i is the number of Characters in the String.

Attempted Solutions

I've tried the following function, via an extension to the String type:

extension String {
func allPossibleCombinations() -> [String] {
        let string = self
        var result = [String]()
        var current = string
        result.append(current)
        for i in 0..<string.count {
            let index = string.index(string.startIndex, offsetBy: i)
            if string[index].isLetter {
                current = current.replacingCharacters(in: index...index, with: String(current[index]).uppercased())
                result.append(current)
            }
        }
        return result
    }

}

This didn't work because it only returns capitalizations in order. For example, if I were to call this method on the String "abc", it would return

["abc", "Abc", "ABc", "ABC"]

This should produce, as stated above, 8 different Strings. I suspect that I need to factor in an exponent to my code, or potentially some form of randomly choosing a Character.

Similar questions that are NOT duplicates

Summary

I am trying to make a function to get all possible capitalization forms of a String. I have a current attempt, but it is not sufficient as it does not produce all possible options.


Solution

  • Compute the integers in the range 0 ..< 2^length and use the bits of the binary representation to tell you when to capitalize a letter:

    extension String {
        func allPossibleCombinations() -> [String] {
            guard self.count > 0 else { return [] }
            var result = [String]()
            let lower = self.lowercased().map(String.init)
            let upper = self.uppercased().map(String.init)
            let length = self.count
            let limit = 1 << length
            for n in 0..<limit {
                var word = ""
                for i in 0..<length {
                    if n & (1 << (length - i - 1)) != 0 {
                        word += upper[i]
                    } else {
                        word += lower[i]
                    }
                }
                result.append(word)
            }
            return result
        }
    }
    

    Examples

    print("abc".allPossibleCombinations())
    
    // ["abc", "abC", "aBc", "aBC", "Abc", "AbC", "ABc", "ABC"]
    
    print("abcd".allPossibleCombinations())
    
    // ["abcd", "abcD", "abCd", "abCD", "aBcd", "aBcD", "aBCd", "aBCD", "Abcd", "AbcD", "AbCd", "AbCD", "ABcd", "ABcD", "ABCd", "ABCD"]
    

    Alex's Swifty Version

    In the comments, Alex added this Swifty version using nested maps:

    extension String {
        func allPossibleCombinations() -> [String] {
            guard self.count > 0 else { return [] }
    
            let length = self.count
            let limit = 1 << self.count
            
            return (0..<limit) .map { n in
                return self.enumerated()
                    .map { i, c in (n & (1 << (length - i - 1)) != 0) ? c.uppercased() : c.lowercased() }
                    .joined()
            }
        }
    }