Apple Swift - Generate combinations with repetition

后端 未结 5 1925
自闭症患者
自闭症患者 2020-12-28 09:25

I\'m trying to generate a nested array containing all combinations with repetition in Apple\'s Swift programming language.

An detailed explanation of combinations wi

相关标签:
5条回答
  • 2020-12-28 09:58

    You can get rid of var sub = subcombo by writing the loop as

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

    This can be further simplified using the map() function:

    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
    }
    

    Update for 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)
    }
    

    Now array slices are passed to the recursive calls to avoid the creation of many temporary arrays.

    Example:

    print(combos(elements: [1, 2, 3], k: 2))
    // [[1, 1], [1, 2], [1, 3], [2, 2], [2, 3], [3, 3]]
    
    0 讨论(0)
  • 2020-12-28 10:02

    Updated @richgordonuk answer for Swift 4 which provides a non-repetitive combination:

    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
    }
    
    0 讨论(0)
  • 2020-12-28 10:08

    Follow up on the existing answers extending RangeReplaceableCollection to support strings as well:

    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"]
    
    0 讨论(0)
  • 2020-12-28 10:12

    Your example gives combinations with repetition. For the record I have written a non-repetitive combination in Swift. I based it on the JavaScript version here: http://rosettacode.org/wiki/Combinations#JavaScript

    I hope it helps others and if anyone can see improvement please do.. note this is my first attempt at Swift and was hoping for a neater way of doing the Swift equivalent of JavaScript slice.

    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)
    

    Per the OP's request, here is a version which removes the custom Array slicing routine in favor of functionality in the standard library

    // 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]]
    
    0 讨论(0)
  • 2020-12-28 10:12

    The main mistake I was making was to use a var named the same as my function:

    combos += combos(array, k)
    

    Which is why I was seeing an error on this line and the other line where my function was being called.

    After fixing that the rest of the problems were easier to solve :)

    In case it will help anyone here's my working function:

    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
    }
    

    If anyone can improve it I'd be happy

    For example can anyone explain how to get rid of the line var sub = subcombo. i.e. how do I make subcombo mutable by default?

    0 讨论(0)
提交回复
热议问题