如何生成所有可能的组合?

时间:2015-10-08 16:20:04

标签: ios arrays swift recursion set

我目前正尝试从Set ArrayStrings制作所有可能组合的Array,每个元素只包含一个字母。

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

Array稍后应包含至少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 ... 长度的所有组合。

我在这里搜索了stackoverflow,但只找到了忽略这个事实的排列函数,每个字母只应该在它们出现时经常使用。

这是我的第一个Swift 2项目,所以请原谅我的greenhornish-ness:)

我想要什么

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

我目前的做法

{{1}}

我知道,这在很多方面都是非常错误的,但是我坚持使用这段代码,并且不知道如何添加递归功能。

4 个答案:

答案 0 :(得分:13)

试试这个。

一般算法是让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<String>。它完成了创建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"]

答案 1 :(得分:9)

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

此处构建了一个包含所有组合的数组(解释 评论内联):

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

我进行了简单的性能比较(在发布模式下编译) 在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
elements:    method:   1st method:  2nd method:

  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

答案 2 :(得分:1)

这是一个快一点的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
   }
}

答案 3 :(得分:0)

在你的输出示例中,不清楚你真正想要的是:

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

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

    ["AB", "AC", "AD", "BC", "BD", "CD", "ABC", "ABD", ...]
    
  3. 我可以为他们推荐@ oisdk的伟大Swift库:SwiftSequence,它有很多有用的功能。在组合部分,他甚至展示了它与Power Set一起使用的示例,这几乎就是您在案例1中寻找的内容。在导入其库的文件后,您可以创建{{1} powerSet上的函数是这样的:

    CollectionType

    这个方法懒惰地评估,这意味着它只在它真正需要时进行评估。现在你提到你只想拥有至少2个元素的组合。使用extension CollectionType { func powerSet() -> LazySequence<FlattenSequence<LazyMapSequence<Self, ComboSeq<Self.Generator.Element>>>>{ var i = 0 return lazy.flatMap{ _ in self.lazyCombos(++i) } } } 方法可以轻松完成此操作:

    filter

    对于案例2.你需要排列的那些,你可以这样做:

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

    您可以将这些转换为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"]] SetString

    Array

    但我强烈建议您使用延迟版;)是的,要删除重复项,您只需使用let array = Array(combPerms) let set = Set(combPerms) 而不只是Set(["A", "B", "C", "D"])

    您也可以像这样一起做案例2.

    ["A", "B", "C", "D"]