【问题标题】:Swift - Generate combinations with repetitionSwift - 生成重复组合
【发布时间】:2014-09-29 12:19:18
【问题描述】:

我正在尝试使用 Apple 的 Swift 编程语言生成一个包含所有重复组合的嵌套数组。

可以在本页底部附近找到关于重复组合的详细说明:http://www.mathsisfun.com/combinatorics/combinations-permutations.html

简单地说;顺序无关紧要,我们可以重复

n = 我们选择形式的事物集合

r = 我们选择的东西的数量

我想创建一个函数,该函数将生成一个嵌套数组,其中包含所有组合,并且对于 n 和 r 的任何(小)值都有重复。

如果有 n=3 个东西可供选择,我们选择其中 r=2 个。

n = [0, 1, 2]
r = 2

函数combos(n: [0, 1, 2], r: 2) 的结果是:

result = [
  [0, 0],
  [0, 1],  
  [0, 2],
  [1, 1],
  [1, 2],
  [2, 2]
]

// we don't need [1, 0], [2, 0] etc. because "order does not matter"

这里有许多编程语言的示例:http://rosettacode.org/wiki/Combinations_with_repetitions

这是 PHP 示例。它是最简单的一种并返回一个数组,这正是我想要的:

function combos($arr, $k) {
    if ($k == 0) {
        return array(array());
    }

    if (count($arr) == 0) {
        return array();
    }

    $head = $arr[0];

    $combos = array();
    $subcombos = combos($arr, $k-1);
    foreach ($subcombos as $subcombo) {
        array_unshift($subcombo, $head);
        $combos[] = $subcombo;
    }
    array_shift($arr);
    $combos = array_merge($combos, combos($arr, $k));
    return $combos;
}

到目前为止,我已经将函数移植到 Swift:

func combos(var array: [Int], k: Int) -> AnyObject { // -> Array<Array<Int>> {
    if k == 0 {
        return [[]]
    }
    
    if array.isEmpty {
        return []
    }
    
    let head = array[0]
    
    var combos = [[]]
    var subcombos: [Array<Int>] = combos(array, k-1)    // error: '(@Ivalue [Int], $T5) -> $T6' is not identical to '[NSArray]'
    for subcombo in subcombos {
        var sub = subcombo
        sub.insert(head, atIndex: 0)
        combos.append(sub)
    }
    array.removeAtIndex(0)
    combos += combos(array, k)    // error: '(@Ivalue [Int], Int) -> $T5' is not identical to '[NSArray]'
    
    return combos
}

大多数情况下,我似乎对各种变量的类型声明以及这些变量是可变的还是不可变的都有问题。

我尝试过使用类型声明更加明确和不明确,但我设法实现的只是错误消息略有不同。

如果有人能解释我哪里出错以及为什么出错,我将不胜感激?

【问题讨论】:

    标签: arrays function swift combinations


    【解决方案1】:

    您可以通过将循环编写为来摆脱var sub = subcombo

    for subcombo in subcombos {
        ret.append([head] + subcombo)
    }
    

    这可以使用map() 函数进一步简化:

    func combos<T>(var array: Array<T>, k: Int) -> Array<Array<T>> {
        if k == 0 {
            return [[]]
        }
    
        if array.isEmpty {
            return []
        }
    
        let head = [array[0]]
        let subcombos = combos(array, k: k - 1)
        var ret = subcombos.map {head + $0}
        array.removeAtIndex(0)
        ret += combos(array, k: k)
    
        return ret
    }
    

    Swift 4 更新:

    func combos<T>(elements: ArraySlice<T>, k: Int) -> [[T]] {
        if k == 0 {
            return [[]]
        }
    
        guard let first = elements.first else {
            return []
        }
    
        let head = [first]
        let subcombos = combos(elements: elements, k: k - 1)
        var ret = subcombos.map { head + $0 }
        ret += combos(elements: elements.dropFirst(), k: k)
    
        return ret
    }
    
    func combos<T>(elements: Array<T>, k: Int) -> [[T]] {
        return combos(elements: ArraySlice(elements), k: k)
    }
    

    现在数组 slices 被传递给递归调用以避免 创建许多临时数组。

    例子:

    print(combos(elements: [1, 2, 3], k: 2))
    // [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
    

    【讨论】:

    • 非常聪明的使用ArraySlice?
    • 您能否添加重复解决方案,例如[[1, 1], [1, 2],[2, 2],[3,1], [1, 3], [2, 2 ], [2, 3], [3, 3]]
    • 如果你想要这个没有重复的版本,你只需要改行:let subcombos = combos(elements: elements, k: k - 1) By let subcombos = combos(elements: elements.dropFirst(), k: k - 1)
    【解决方案2】:

    你的例子给出了重复的组合。作为记录,我在 Swift 中编写了一个非重复的组合。我在这里基于 JavaScript 版本:http://rosettacode.org/wiki/Combinations#JavaScript

    我希望它可以帮助其他人,如果有人能看到改进,请这样做。请注意,这是我第一次尝试 Swift,并希望有一种更简洁的方式来做相当于 JavaScript 切片的 Swift。

    func sliceArray(var arr: Array<Int>, x1: Int, x2: Int) -> Array<Int> {
        var tt: Array<Int> = []
        for var ii = x1; ii <= x2; ++ii {
            tt.append(arr[ii])
        }
        return tt
    }
    
    func combinations(var arr: Array<Int>, k: Int) -> Array<Array<Int>> {
        var i: Int
        var subI : Int
    
        var ret: Array<Array<Int>> = []
        var sub: Array<Array<Int>> = []
        var next: Array<Int> = []
        for var i = 0; i < arr.count; ++i {
            if(k == 1){
                ret.append([arr[i]])
            }else {
                sub = combinations(sliceArray(arr, i + 1, arr.count - 1), k - 1)
                for var subI = 0; subI < sub.count; ++subI {
                    next = sub[subI]
                    next.insert(arr[i], atIndex: 0)
                    ret.append(next)
                }
            }
    
        }
        return ret
    }
    
    
    var myCombinations = combinations([1,2,3,4],2)
    

    根据 OP 的要求,这是一个删除自定义数组切片例程以支持标准库中的功能的版本

    // Calculate the unique combinations of elements in an array
    // taken some number at a time when no element is allowed to repeat
    func combinations<T>(source: [T], takenBy : Int) -> [[T]] {
        if(source.count == takenBy) {
            return [source]
        }
    
        if(source.isEmpty) {
            return []
        }
    
        if(takenBy == 0) {
            return []
        }
    
        if(takenBy == 1) {
            return source.map { [$0] }
        }
    
        var result : [[T]] = []
    
        let rest = Array(source.suffixFrom(1))
        let sub_combos = combinations(rest, takenBy: takenBy - 1)
        result += sub_combos.map { [source[0]] + $0 }
    
        result += combinations(rest, takenBy: takenBy)
    
        return result
    }
    
    var myCombinations = combinations([1,2,3,4], takenBy: 2)
    // myCombinations = [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]
    

    【讨论】:

    • 您可以使用范围对数组进行切片,例如arr[1...3] 从数组中提取第二个、第三个和第四个元素。在您的代码中,调用将是 arr[i+1..&lt;arr.count]
    【解决方案3】:

    更新了 Swift 4 的 @richgordonuk 答案,提供了非重复组合:

    func combinations<T>(source: [T], takenBy : Int) -> [[T]] {
        if(source.count == takenBy) {
            return [source]
        }
    
        if(source.isEmpty) {
            return []
        }
    
        if(takenBy == 0) {
            return []
        }
    
        if(takenBy == 1) {
            return source.map { [$0] }
        }
    
        var result : [[T]] = []
    
        let rest = Array(source.suffix(from: 1))
        let subCombos = combinations(source: rest, takenBy: takenBy - 1)
        result += subCombos.map { [source[0]] + $0 }
        result += combinations(source: rest, takenBy: takenBy)
        return result
    }
    

    【讨论】:

      【解决方案4】:

      跟进扩展 RangeReplaceableCollection 以支持字符串的现有答案:

      extension RangeReplaceableCollection {
          func combinations(of n: Int) -> [SubSequence] {
              guard n > 0 else { return [.init()] }
              guard let first = first else { return [] }
              return combinations(of: n - 1).map { CollectionOfOne(first) + $0 } + dropFirst().combinations(of: n)
          }
          func uniqueCombinations(of n: Int) -> [SubSequence] {
              guard n > 0 else { return [.init()] }
              guard let first = first else { return [] }
              return dropFirst().uniqueCombinations(of: n - 1).map { CollectionOfOne(first) + $0 } + dropFirst().uniqueCombinations(of: n)
          }
      }
      

      [1, 2, 3, 4, 5, 6].uniqueCombinations(of: 2)  // [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3], [2, 4], [2, 5], [2, 6], [3, 4], [3, 5], [3, 6], [4, 5], [4, 6], [5, 6]]
      
      "abcdef".uniqueCombinations(of: 3) // ["abc", "abd", "abe", "abf", "acd", "ace", "acf", "ade", "adf", "aef", "bcd", "bce", "bcf", "bde", "bdf", "bef", "cde", "cdf", "cef", "def"]
      

      【讨论】:

        【解决方案5】:

        您可以为此使用 Apple 的新库:https://github.com/apple/swift-algorithms/blob/main/Guides/Combinations.md

        let numbers = [10, 20, 30, 40]
        for combo in numbers.combinations(ofCount: 2) {
            print(combo)
        }
        // [10, 20]
        // [10, 30]
        // [10, 40]
        // [20, 30]
        // [20, 40]
        // [30, 40]
        

        【讨论】:

          【解决方案6】:

          我犯的主要错误是使用了与我的函数同名的 var:

          combos += combos(array, k)
          

          这就是为什么我在此行和调用我的函数的另一行上看到错误的原因。

          修复后剩下的问题就更容易解决了:)

          如果它可以帮助任何人,这是我的工作职能:

          func combos<T>(var array: Array<T>, k: Int) -> Array<Array<T>> {
              if k == 0 {
                  return [[]]
              }
          
              if array.isEmpty {
                  return []
              }
          
              let head = array[0]
          
              var ret: Array<Array<T>> = []
              var subcombos = combos(array, k - 1)
              for subcombo in subcombos {
                  var sub = subcombo
                  sub.insert(head, atIndex: 0)
                  ret.append(sub)
              }
              array.removeAtIndex(0)
              ret += combos(array, k)
          
              return ret
          }
          

          如果有人可以改进它,我会很高兴

          例如,任何人都可以解释如何摆脱var sub = subcombo这一行。即我如何使 subcombo 默认可变?

          【讨论】:

            【解决方案7】:

            对相同算法的函数式处理:

            func headTail<C: Collection>(_ c: C) -> (C.Element, C.SubSequence)? {
                if c.isEmpty { return nil }
                else { return (c.first!, c.dropFirst()) }
            }
            
            func combos<C: Collection>(_ c: C, by k: Int) -> [[C.Element]] {
                if k <= 0 { return [[]] }
                else if let (head, tail) = headTail(c) {
                    return combos(c, by: k-1).map { [head] + $0 } + combos(tail, by: k)
                } else { return [] }
            }
            

            或者,作为Collection上的成员函数:

            extension Collection {
                var headTail: (Element, SubSequence)? {
                    if isEmpty { return nil }
                    else { return (first!, dropFirst()) }
                }
                
                func combos(by k: Int) -> [[Element]] {
                    if k <= 0 { return [[]] }
                    else if let (head, tail) = headTail {
                        return combos(by: k-1).map { [head] + $0 } + tail.combos(by: k)
                    } else { return [] }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2018-12-16
              • 1970-01-01
              • 2017-05-19
              • 1970-01-01
              • 2015-04-01
              • 2017-11-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多