Swift - 生成重复组合

Posted

技术标签:

【中文标题】Swift - 生成重复组合【英文标题】:Swift - Generate combinations with repetition 【发布时间】: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

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

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

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

【问题讨论】:

【参考方案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 [] 
    

【讨论】:

以上是关于Swift - 生成重复组合的主要内容,如果未能解决你的问题,请参考以下文章

从具有重复元素的向量生成所有唯一组合

生成所有可能的组合 - Java [重复]

为X位数生成0-9的所有唯一组合[重复]

使用 MATLAB 生成所有重复组合

swift算法:数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

如何有效地生成组合而不重复,它们之间有特定的数字