The OP clarified in comments that he wanted the case-variants of the original string, not "subsets" as originally stated
[Edit] I originally had a paragraph here about String.count
, however, my memory must have been in error, because Apple's documentation does state that String.count
is in fact the number of Character
s, which is what we would all want it to be anyway. I hope my error didn't throw anyone too far off.
You don't need any counters. All you need is the first character, and recurse on the rest of the string.
The thing is, when you have a letter as your first you need to preprend both the upper and lower case variants to all of the strings returned by the recursive call.
The base case is at the end of the string, in which case you return an an array containing just the empty string.
Here's my implementation:
func caseVariants(of s: String) -> [String]
{
func caseVariants(of s: Substring) -> [String]
{
guard let c = s.first else { return [""] } // base case
let remainder = s[s.index(after: s.startIndex)...]
let remainderVariants = caseVariants(of: remainder)
var results: [String] = []
if c.isLetter
{
results.append(
contentsOf: remainderVariants.map {
"\(c.uppercased())" + $0
}
)
results.append(
contentsOf: remainderVariants.map {
"\(c.lowercased())" + $0
}
)
}
else
{
results.append(
contentsOf: remainderVariants.map { "\(c)" + $0 }
)
}
return results
}
return caseVariants(of: s[...]).sorted()
}
print("Case variants:")
for s in caseVariants(of: "a1b2") { print("\"\(s)\"") }
The output is:
Case variants:
"A1B2"
"A1b2"
"a1B2"
"a1b2"
[EDIT] in comments, OP asked what if .startIndex
were disallowed (such as in an interview). While I think such a restriction is insane, there is an easy solution, and it's a one-line, quite reasonable change to my previous code. Change this line:
let remainder = s[s.index(after: s.startIndex)...]
to use .dropFirst()
let remainder = s.dropFirst()
If we look at the implementation of dropFirst
in the Collection
protocol in the standard library:
@inlinable
public __consuming func dropFirst(_ k: Int = 1) -> SubSequence {
_precondition(k >= 0, "Can't drop a negative number of elements from a collection")
let start = index(startIndex, offsetBy: k, limitedBy: endIndex) ?? endIndex
return self[start..<endIndex]
}
We see that the use of dropFirst
will use the default value of 1
for k
. In that case, when we've already checked that we're not at the end of the string, the line
let start = index(startIndex, offsetBy: k, limitedBy: endIndex) ?? endIndex
is equivalent to
let start = index(after: startIndex)
which means that the returned substring is
return self[index(after: startIndex)..<endIndex]
which is just the canonical way of saying:
return self[index(after: startIndex)...]
So a version using dropFirst()
is identical to the original solution once inlining has done its thing.