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..<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 - 生成重复组合的主要内容,如果未能解决你的问题,请参考以下文章