【问题标题】:How to generate all possible combinations?如何生成所有可能的组合?
【发布时间】:2015-10-08 16:20:03
【问题描述】:

我目前正在尝试从ArrayStrings 中创建所有可能组合的Set,每个元素仅包含一个字母。

Array 本身可以包含相同的字母两次甚至更多次,并且它们只能在出现时使用。

Set 稍后应包含从至少 2 个字母到给定 Array 长度的所有组合。

我在 stackoverflow 上搜索了这里,但只找到了忽略这个事实的置换函数,即每个字母只能在出现时使用。

这是我的第一个 Swift 2 项目,所以请原谅我的新手:)

我想要什么

var array = ["A", "B", "C","D"]
var combinations: Set<String>

... <MAGIC> ...

print(combinations)
// "AB", "ABC", "ABD", "ABCD", "ABDC", "AC", "ACB", "ACD", "ACBD", "ACDB", and so on ...

我目前的做法

func permuation(arr: Array<String>) {

    for (index, elementA) in arr.enumerate() {
        //1..2..3..4
        var tmpString = elementA
        var tmpArray = arr
        tmpArray.removeAtIndex(index)

        for (index2, elementB) in tmpArray.enumerate() {
            // 12..13..14
            var tmpString2 = tmpString + elementB
            var tmpArray2 = tmpArray

            //[3,4]
            tmpArray2.removeAtIndex(index2)

            results.append(tmpString2)
        }
    }

}
permuation(array)
print(results)
// "["AB", "AC", "AD", "BA", "BC", "BD", "CA", "CB", "CD", "DA", "DB", "DC"]"

我知道,这在很多方面都非常错误,但我被这段代码卡住了,不知道如何添加递归功能。

【问题讨论】:

    标签: ios arrays swift recursion set


    【解决方案1】:

    试试这个。

    一般的算法是有一个 fromList 包含你还没有使用的字母和一个 toList 这是你到目前为止建立的字符串。这使用递归来构建所有可能的字符串,并在长度为 2 或更大时将它们添加到集合中:

    func permute(fromList: [String], toList: [String] = [String](), var set: Set<String> = Set<String>()) -> Set<String> {
        if toList.count >= 2 {
            set.insert(toList.joinWithSeparator(""))
        }
        if !fromList.isEmpty {
            for (index, item) in fromList.enumerate() {
                var newFrom = fromList
                newFrom.removeAtIndex(index)
                set = permute(newFrom, toList: toList + [item], set: set)
            }
        }
        return set
    }
    
    permute(["A", "B", "C"])
    // {"BA", "AC", "ABC", "AB", "BCA", "CB", "BC", "CAB", "ACB", "CA", "CBA", "BAC"}
    
    permute(["A", "A", "B"])
    // {"BA", "BAA", "AAB", "AB", "ABA", "AA"}
    

    更快的回答:

    正如@MartinR 在他的帖子中指出的那样,由于所有集合的创建和复制,上述解决方案有点慢。我最初是使用 inout 变量作为 set 编写的,但将其更改为功能更强大的接口以使其易于调用。

    这是我最初的(更快的)实现,另外我将它嵌入到一个 permute 中,它只需要一个 [String] 并返回一个 Set&lt;String&gt;。它负责创建settoList 数组,然后调用permute 的内部版本来完成真正的工作:

    func permute(list: [String], minStringLen: Int = 2) -> Set<String> {
        func permute(fromList: [String], toList: [String], minStringLen: Int, inout set: Set<String>) {
            if toList.count >= minStringLen {
                set.insert(toList.joinWithSeparator(""))
            }
            if !fromList.isEmpty {
                for (index, item) in fromList.enumerate() {
                    var newFrom = fromList
                    newFrom.removeAtIndex(index)
                    permute(newFrom, toList: toList + [item], minStringLen: minStringLen, set: &set)
                }
            }
        }
    
        var set = Set<String>()
        permute(list, toList:[], minStringLen: minStringLen, set: &set)
        return set
    }
    
    permute(["A", "B", "C"])
    // {"BA", "AC", "ABC", "AB", "BCA", "CB", "BC", "CAB", "ACB", "CA", "CBA", "BAC"}
    
    permute(["A", "A", "B"])
    // {"BA", "BAA", "AAB", "AB", "ABA", "AA"}
    
    permute(["A", "A", "B"], minStringLen: 1)
    // {"BA", "A", "BAA", "AB", "AA", "B", "AAB", "ABA"}
    
    permute(["A", "A", "B"], minStringLen: 3)
    // {"ABA", "BAA", "AAB"}
    

    编辑: 我添加了一个minStringLen 参数(默认值为2),而不是对该值进行硬编码。

    有关性能比较,请参阅 @MartinR 的答案。


    Swift 3 和 Swift 4:

    func permute(list: [String], minStringLen: Int = 2) -> Set<String> {
        func permute(fromList: [String], toList: [String], minStringLen: Int, set: inout Set<String>) {
            if toList.count >= minStringLen {
                set.insert(toList.joined(separator: ""))
            }
            if !fromList.isEmpty {
                for (index, item) in fromList.enumerated() {
                    var newFrom = fromList
                    newFrom.remove(at: index)
                    permute(fromList: newFrom, toList: toList + [item], minStringLen: minStringLen, set: &set)
                }
            }
        }
    
        var set = Set<String>()
        permute(fromList: list, toList:[], minStringLen: minStringLen, set: &set)
        return set
    }
    
    print(permute(list: ["A", "B", "C"]))
    // ["ABC", "CA", "BAC", "ACB", "BA", "CAB", "BC", "CB", "BCA", "CBA", "AB", "AC"]
    
    print(permute(list: ["A", "A", "B"]))
    // ["AA", "AAB", "ABA", "AB", "BA", "BAA"]
    
    print(permute(list: ["A", "A", "B"], minStringLen: 1))
    // ["AAB", "ABA", "B", "BA", "A", "BAA", "AA", "AB"]
    
    print(permute(list: ["A", "A", "B"], minStringLen: 3))
    // ["AAB", "ABA", "BAA"]
    

    【讨论】:

    • 我注意到,如果您将 single set 作为 inout 参数传递给递归调用,那么您的方法会变得更快(实际上比我的更快)诚实:)
    • 我最初是这样写的。我应该将该版本添加到答案中。
    • 谢谢你们!我想接受这两个答案,但这是不可能的。 :) 但是,如果您想按上述方式编辑您的,我会将其标记为正确。
    • 我已经用我原来的(和更快的)解决方案更新了答案。感谢 @MartinR 的性能测量,并建议 inout 毕竟是更好的选择。
    【解决方案2】:

    这与@vacawama 的答案非常相似,但希望有所不同 足够它值得一个单独的答案:)

    这里,构建了一个包含 all 组合的数组(解释 cmets 内联):

    func combinations(array : [String]) -> [String] {
    
        // Recursion terminates here:
        if array.count == 0 { return [] }
    
        // Concatenate all combinations that can be built with element #i at the
        // first place, where i runs through all array indices:
        return array.indices.flatMap { i -> [String] in
    
            // Pick element #i and remove it from the array:
            var arrayMinusOne = array
            let elem = arrayMinusOne.removeAtIndex(i)
    
            // Prepend element to all combinations of the smaller array:
            return [elem] + combinations(arrayMinusOne).map { elem + $0 }
        }
    }
    

    然后您可以过滤至少包含两个字母的字符串,然后 将其转换为Set:

    let c = Set(combinations(["A", "B", "C"]).filter { $0.characters.count >= 2 })
    print(c)
    // ["BA", "AC", "ABC", "AB", "BCA", "CB", "BC", "CAB", "ACB", "CA", "CBA", "BAC"]
    

    我做了一个简单的性能比较(在Release模式下编译 在 Macbook Pro 上):

    let array = ["A", "B", "C", "D", "E", "F", "G"]
    
    let t1 = NSDate()
    let c1 = Set(combinations(array).filter { $0.characters.count >= 2 })
    let t2 = NSDate()
    let c2 = permute(array)
    let t3 = NSDate()
    
    print(c1 == c2) // true
    print(t2.timeIntervalSinceDate(t1))
    print(t3.timeIntervalSinceDate(t2))
    

    结果取决于输入数组的大小, 但是@vacawama 的更新方法是最快的:

    # of array This vacawama's vacawama's 要素: 方法: 第一种方法: 第二种方法: 2 0.00016 0.00005 0.00001 3 0.00043 0.00013 0.00004 4 0.00093 0.00062 0.00014 5 0.00335 0.00838 0.00071 6 0.01756 0.24399 0.00437 7 0.13625 11.90969 0.03692

    【讨论】:

    • 既然您已经有了一个测试工具,那么首先将数组传递给一个集合以删除任何重复项会有所帮助吗?我有一种感觉,需要的时间被更少的元素置换所抵消。
    • @Abizern:combinations() 返回的数组不包含重复元素(除非输入数组有重复元素,在这种情况下,根据问题描述需要重复组合)。
    • 感谢您的友好回答,尤其是您的性能测试。 :)
    • 嗯,我都测试过(你和@vacawama 的答案),似乎没有明确的赢家。结果模棱两可。
    • 似乎一切都很重要。我的意思是,如果你改变执行顺序,结果也会改变。首先,我将您的方法放在首位,它比 vacawama 的要慢一些。但后来我改变了订单,你的订单做得更好。在这里,我将结果发布在带有 7 个字母的真实设备上。你的 0.121307969093323 和 vacawama 的 0.134603023529053
    【解决方案3】:

    这是一个速度更快的 Swift 3 函数。它基于 Array 类型的扩展,可用于任何元素类型的数组。

    public func allCombinations(_ array:[String], minLength:Int=2) -> [String]
    {
       var result:[String] = []
       for n in minLength...array.count
       {
          result = result + array.combinations(of:n).map{ $0.joined(separator:"") }
       }
       return result
    }
    
    extension Array
    {
        public func combinations(of group:Int) -> [[Element]]
        {
           if group > count  { return [] }
    
           if group == count { return [self] }
    
           var result:[[Element]] = []
    
           var comboIndexes = (0..<group).map{$0}
    
           let fullCombo   = group - 1
           let indexLimit  = count - fullCombo
    
           var carry = fullCombo
    
           while carry >= 0
           {
              if carry == fullCombo
              { result.append(comboIndexes.map{self[$0]}) }
    
              comboIndexes[carry] += 1
    
              if comboIndexes[carry] == carry + indexLimit 
              { carry -= 1 ; continue }
    
              while carry < fullCombo
              {
                 carry += 1
                 comboIndexes[carry] = comboIndexes[carry-1] + 1 
              }       
           }
    
           return result
       }
    }
    

    在我的测试中,它在 7 个字母上的运行速度比 vacawama 的第二个版本快了大约 40 倍。

    [编辑] 后来我意识到这个函数会产生组合(按照 OP 中的要求),而 vacawama 的函数会产生排列。我测试了一个等效的置换算法,它只比 vacawama 的快 55%。

    extension Array
    {
       public func permutations(of group:Int? = nil) -> [[Element]]
       {
          let group       = group ?? count
          var result      : [[Element]] = []
          var permutation : [Element]   = []
    
          func permute(from baseIndex:Int)
          {
             if baseIndex == permutation.count - 1
             { 
               result.append(permutation)
               return 
             }
    
             permute(from:baseIndex+1)
    
             for index in baseIndex+1..<permutation.count
             {
                swap(&permutation[baseIndex],&permutation[index]) 
                permute(from:baseIndex+1)
             }
             let baseElement = permutation[baseIndex]
             permutation.remove(at:baseIndex)
             permutation.append(baseElement)
          }
    
          var comboIndexes = (0..<group).map{$0}
    
          let fullCombo   = group - 1
          let indexLimit  = count - fullCombo
    
          var carry = fullCombo
    
          while carry >= 0
          {
             if carry == fullCombo
             { 
               permutation = comboIndexes.map{self[$0]}
               permute(from:0)
             }
    
             comboIndexes[carry] += 1
    
             if comboIndexes[carry] == carry + indexLimit 
             { carry -= 1 ; continue }
    
             while carry < fullCombo
             {
                carry += 1
                comboIndexes[carry] = comboIndexes[carry-1] + 1 
             }       
          }
    
          return result
       }
    }
    

    【讨论】:

      【解决方案4】:

      在您的输出示例中,您也不清楚您真正想要什么:

      1. 它们的所有组合和排列:

        ["AB", "BA", "AC", "CA", "AD", "DA", ..., "ABCD", "ABDC", "ACBD", "ACDB", ...]
        
      2. 只是所有组合:

        ["AB", "AC", "AD", "BC", "BD", "CD", "ABC", "ABD", ...]
        

      我可以为他们推荐@oisdk 的优秀 Swift 库:SwiftSequence,它有很多有用的功能。在组合部分,他甚至展示了它与Power Set 一起使用的示例,这几乎正是您在案例 1 中所要查找的内容。导入他的库文件后,您可以在 @ 上创建 powerSet 函数987654326@s 这样的:

      extension CollectionType {
          func powerSet() -> LazySequence<FlattenSequence<LazyMapSequence<Self, ComboSeq<Self.Generator.Element>>>>{
              var i = 0
              return lazy.flatMap{ _ in self.lazyCombos(++i) }
          }
      }
      

      此方法延迟评估,这意味着它仅在真正需要时才评估。现在您提到您只想拥有至少 2 个元素的组合。这可以通过filter 方法轻松完成:

      let combinations = ["A", "B", "C", "D"].powerSet().filter{ $0.count >= 2 }
          // As an array: [["A", "B"], ["A", "C"], ["A", "D"], ["B", "C"], ["B", "D"], ["C", "D"], ["A", "B", "C"], ["A", "B", "D"], ["A", "C", "D"], ["B", "C", "D"], ["A", "B", "C", "D"]]
      

      对于情况 2。如果您还需要这些排列,您可以这样做:

      let combPerms = combinations.flatMap{ $0.permutations() }
          // As an array: [["A", "B"], ["B", "A"], ["A", "C"], ["C", "A"], ["A", "D"], ["D", "A"], ["B", "C"], ["C", "B"], ["B", "D"], ["D", "B"], ["C", "D"], ["D", "C"], ["A", "B", "C"], ["A", "C", "B"], ["B", "A", "C"], ["B", "C", "A"], ["C", "A", "B"], ["C", "B", "A"], ["A", "B", "D"], ["A", "D", "B"], ["B", "A", "D"], ["B", "D", "A"], ["D", "A", "B"], ["D", "B", "A"], ["A", "C", "D"], ["A", "D", "C"], ["C", "A", "D"], ["C", "D", "A"], ["D", "A", "C"], ["D", "C", "A"], ["B", "C", "D"], ["B", "D", "C"], ["C", "B", "D"], ["C", "D", "B"], ["D", "B", "C"], ["D", "C", "B"], ["A", "B", "C", "D"], ["A", "B", "D", "C"], ["A", "C", "B", "D"], ["A", "C", "D", "B"], ["A", "D", "B", "C"], ["A", "D", "C", "B"], ["B", "A", "C", "D"], ["B", "A", "D", "C"], ["B", "C", "A", "D"], ["B", "C", "D", "A"], ["B", "D", "A", "C"], ["B", "D", "C", "A"], ["C", "A", "B", "D"], ["C", "A", "D", "B"], ["C", "B", "A", "D"], ["C", "B", "D", "A"], ["C", "D", "A", "B"], ["C", "D", "B", "A"], ["D", "A", "B", "C"], ["D", "A", "C", "B"], ["D", "B", "A", "C"], ["D", "B", "C", "A"], ["D", "C", "A", "B"], ["D", "C", "B", "A"]]
      

      您可以将这些转换为 SetStrings 或 Array

      let array = Array(combPerms)
      let set = Set(combPerms)
      

      但我强烈建议使用惰性版本 ;) 是的,要删除重复项,您只需使用 Set(["A", "B", "C", "D"]) 而不是 ["A", "B", "C", "D"]

      您也可以像这样一次性完成案例 2:

      let x = ["A", "B", "C", "D"]
      
      let result = Set(
          x.indices
              .flatMap{ x.lazyCombos($0 + 1) }
              .filter{ $0.count >= 2 }
              .flatMap{ $0.permutations() }
              .map{ $0.joinWithSeparator("") })
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-01-07
        • 1970-01-01
        • 2022-01-18
        • 2022-08-22
        相关资源
        最近更新 更多