Treating you permutations as sequential numbers in a custom positional numeral system
"AAA, AAB, AAC, AAD, AAE, ABA, ACA, ..."
As per your example, you basically want to permute unique single-letter strings with replacement; with fixed sample size (above; 3). If so, you could consider your letters as unique digits in a custom numeral positional numeral system, for you example specifically a radix 5 system for which you'd like to compute all numbers expressible with at most 3 digits. Finally, you'd like to pad all numbers with leading "zeros" (A
), in case you are using less digits than allowed (<3
).
With this special case in mind, we can readily make use of the String(_:radix:)
and Int(_:radix:)
initializers to convert base 10 numbers to your specific numeral system, and implement a non-recursive approach as follows:
// Helper to pad the presented numbers to a given width.
extension String {
func leftPadded(with padding: Character, toAtLeast width: Int) -> String {
return count >= width ? self
: String(repeating: padding, count: width - count) + self
}
}
let digits = ["A", "B", "C", "D", "E"]
let base = digits.count
let width = 3
if let zero = digits.first.map(Character.init) {
// Iterate and convert to your numeral system.
for i in 0..<((0..<width).reduce(1) { (p, _) in p * base }) {
let nonPaddedPermutation = String(i, radix: base)
.flatMap { Int(String($0), radix: base) }
.map { String(digits[$0]) }
.joined()
print(nonPaddedPermutation.leftPadded(with: zero, toAtLeast: width))
} /* AAA
AAB
...
EED
EEE */
}
Or, somewhat more general (treating allowed digits as characters rather than single-character strings):
extension String {
func leftPadded(with padding: Character, toAtLeast width: Int) -> String {
return count >= width ? self
: String(repeating: padding, count: width - count) + self
}
}
extension Array where Element == Character {
// Limitation: all elements are unique (otherwise: `nil` return)
func replacementPermute(sampleSize width: Int) -> [String]? {
guard count == Set(self).count else { return nil }
var permutations: [String] = []
if let zero = first {
let numPerms = ((0..<width).reduce(1) { (p, _) in p * count })
permutations.reserveCapacity(numPerms)
for i in 0..<numPerms {
let nonPaddedPermutation = String(i, radix: count)
.flatMap { Int(String($0), radix: count) }
.map { String(self[$0]) }
.joined()
permutations.append(nonPaddedPermutation
.leftPadded(with: zero, toAtLeast: width))
}
}
return permutations
}
}
// Example usage:
if let permutations = ["A", "", "C", "D", "E"]
.flatMap(Character.init).replacementPermute(sampleSize: 3) {
print(permutations)
// ["AAA", "AA", "AAC", ... "EEA", "EE", "EEC", "EED", "EEE"]
}