【问题标题】:swift string permutations allowing the same strings允许相同字符串的快速字符串排列
【发布时间】:2018-02-25 16:57:48
【问题描述】:

我看过其他关于字符串排列的问题,但它们并没有完全涵盖我的问题。

假设我有一个字符串数组:["A", "B", "C", "D", "E"],我正在寻找一种方法来获取所有可能的组合,例如三个元素:

AAA, AAB, AAC, AAD, AAE, ABA, ACA, ...

其他排列解决方案(例如herehere)不允许重复相同的元素,并导致:

ABC、ABD、ABE、BAC、...

我现在使用蛮力方法,有很多次迭代,但当然是超级慢(因为单个字符串的数量可能超过 10 个)

任何想法如何解决这个问题?

这就是我现在拥有的:

func getVariations() -> [String] {
var variations = [String]()

let elements = ["A", "B", "C", "D", "E"]

for e1 in elements {
    variations.append(e1)

    for e2 in elements {
        variations.append(e1 + e2)

        for e3 in elements {
            variations.append(e1 + e2 + e3)

            for e4 in elements {
                variations.append(e1 + e2 + e3 + e4)
            }
        }
    }

    return variations
}

可以想象,当要添加更多元素时,这会失控。

【问题讨论】:

标签: swift string permutation


【解决方案1】:

another question 中,您询问如何过滤来自 dfri 的答案 (+1) 的结果,以删除由不同元素顺序导致的重复项(例如,如果您的结果集包含“AAB”、“ABA”、和“BAA”,去掉后两者)。

如果这是您想要做的,我建议编写一个直接返回该组解决方案的函数:

extension Array where Element: StringProtocol {

    /// Return combinations of the elements of the array (ignoring the order of items in those combinations).
    ///
    /// - Parameters:
    ///   - size: The size of the combinations to be returned.
    ///   - allowDuplicates: Boolean indicating whether an item in the array can be repeated in the combinations (e.g. is the sampled item returned to the original set or not).
    ///
    /// - Returns: A collection of resulting combinations.

    func combinations(size: Int, allowDuplicates: Bool = false) -> [String] {
        let n = count

        if size > n && !allowDuplicates { return [] }

        var combinations: [String] = []

        var indices = [0]

        var i = 0

        while true {
            // build out array of indexes (if not complete)

            while indices.count < size {
                i = indices.last! + (allowDuplicates ? 0 : 1)
                if i < n {
                    indices.append(i)
                }
            }

            // add combination associated with this particular array of indices

            combinations.append(indices.map { self[$0] }.joined())

            // prepare next one (incrementing the last component and/or deleting as needed

            repeat {
                if indices.count == 0 { return combinations }
                i = indices.last! + 1
                indices.removeLast()
            } while i > n - (allowDuplicates ? 1 : (size - indices.count))
            indices.append(i)
        }
    }
}

因此:

let array = ["A", "B", "C", "D"]
let result = array.combinations(size: 2, allowDuplicates: true)

会返回:

[“AA”、“AB”、“AC”、“AD”、“BB”、“BC”、“BD”、“CC”、“CD”、“DD”]

如果你不希望它允许重复:

let result = array.combinations(size: 2)

会返回:

[“AB”、“AC”、“AD”、“BC”、“BD”、“CD”]

这种方法可以避免later filter the results

请注意,我确信有更优雅的方法可以实现上述目标,但希望这能说明基本思想。

【讨论】:

  • 这个答案实际上解决了这个问题和第二个问题(见链接)。而且它也比我尝试过的任何其他解决方案都要快。
  • 一个快速的后续问题:输入确实是“ABCD”(所以一个字符串),作为一种解决方法,我使用"ABCD".map { String($0) } 能够使用上面的combinations 函数。这听起来像是一种更合乎逻辑的方法吗,或者在这种情况下,扩展名应该是String
  • 虽然上面我试图保持上面的简单,但我实际上会将combinations 更改为不需要StringProtocol 一致性,因此它可以与任何数组一起使用。然后创建一个String 方法来执行map 到一个字符/字符串数组,然后在最后重新加入它们。见gist.github.com/robertmryan/1ca0deab3e3e53d54dccf421a5c64144
  • 不错的改进,感谢分享。现在,如果输入是“ABCA”,结果将同时包含“AC”和“CA”,以及“AB”和“BA”。所以为了过滤掉这些,我可以按字母顺序对每个字符串进行排序,然后将字符串数组转换为Set,然后再转换回Array——这听起来像是一种方法吗?
  • 目标是从组合结果数组中删除重复项,所以 "ABCA" -> ["AB", "AC", "AA", "BC", "BA", "CA "] -> ["AB", "AC", "AA", "BC"] - 很抱歉造成混淆。
【解决方案2】:

将您的排列视为自定义位置数字系统中的序列号

“AAA, AAB, AAC, AAD, AAE, ABA, ACA, ...”

根据您的示例,您基本上希望通过替换来置换唯一的单字母字符串;具有固定样本量(以上;3)。如果是这样,您可以将您的字母视为自定义数字位置数字系统中的唯一数字,例如,特别是一个基数 5 系统,您希望为其计算最多可以用 3 位数字表示的所有数字。最后,您希望使用前导“零”(A) 填充所有数字,以防您使用的位数少于允许的位数 (&lt;3)。

考虑到这种特殊情况,我们可以轻松地使用 String(_:radix:)Int(_:radix:) 初始化器将基数为 10 的数字转换为您的特定数字系统,并实现如下非递归方法:

// 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 */
}

或者,更通用一些(将允许的数字视为字符而不是单个字符串):

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"]
}

【讨论】:

  • 我认为如果使用幂计算,排列的数量会更容易阅读:Int(pow(Double(base), Double(width)))。如果你只想使用整数运算,那么repeatElement(base, count: width).reduce(1, *).
  • 这绝对比我的蛮力方法快。我还将尝试@Martin R 建议的链接,稍后将在此处发布我的发现。
  • @Sulthan 我故意避免pow 和转换,但repeatElement 很简洁,谢谢!稍后会更新我的答案。
  • 我更喜欢这种方法,因为它允许我将 sampleSize 限制为一个值,而其他解决方案提供所有可能的组合。换句话说,我只会得到 "AAA", "AA?", "AAC", ... 而不是 `"AA", "A?", ...
  • 下一步是如何过滤出具有相同字母组合的结果(我不需要“AAC”和“ACA”),但如果我无法解决这个问题,我会发布另一个问题。
猜你喜欢
  • 1970-01-01
  • 2015-11-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-26
相关资源
最近更新 更多