Skip to content

Commit 5b7993c

Browse files
authored
All combinations (#51)
Add an overload of combinations(ofCount:) that accepts a range.
1 parent 3864606 commit 5b7993c

File tree

4 files changed

+257
-31
lines changed

4 files changed

+257
-31
lines changed

Guides/Combinations.md

+24-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,27 @@ for combo in numbers2.combinations(ofCount: 2) {
3636
// [10, 10]
3737
```
3838

39+
Given a range, the `combinations(ofCount:)` method returns a sequence of all
40+
the different combinations of the given sizes of a collection’s elements in
41+
increasing order of size.
42+
43+
```swift
44+
let numbers = [10, 20, 30, 40]
45+
for combo in numbers.combinations(ofCount: 2...3) {
46+
print(combo)
47+
}
48+
// [10, 20]
49+
// [10, 30]
50+
// [10, 40]
51+
// [20, 30]
52+
// [20, 40]
53+
// [30, 40]
54+
// [10, 20, 30]
55+
// [10, 20, 40]
56+
// [10, 30, 40]
57+
// [20, 30, 40]
58+
```
59+
3960
## Detailed Design
4061

4162
The `combinations(ofCount:)` method is declared as a `Collection` extension,
@@ -56,9 +77,9 @@ array at every index advancement. `Combinations` does conform to
5677

5778
### Complexity
5879

59-
Calling `combinations(ofCount:)` accesses the count of the collection, so it’s an
60-
O(1) operation for random-access collections, or an O(_n_) operation otherwise.
61-
Creating the iterator for a `Combinations` instance and each call to
80+
Calling `combinations(ofCount:)` accesses the count of the collection, so it’s
81+
an O(1) operation for random-access collections, or an O(_n_) operation
82+
otherwise. Creating the iterator for a `Combinations` instance and each call to
6283
`Combinations.Iterator.next()` is an O(_n_) operation.
6384

6485
### Naming

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
88

99
#### Combinations / permutations
1010

11-
- [`combinations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Combinations.md): Combinations of a particular size of the elements in a collection.
11+
- [`combinations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Combinations.md): Combinations of particular sizes of the elements in a collection.
1212
- [`permutations(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Permutations.md): Permutations of a particular size of the elements in a collection, or of the full collection.
1313

1414
#### Mutating algorithms

Sources/Algorithms/Combinations.swift

+145-21
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift Algorithms open source project
44
//
5-
// Copyright (c) 2020 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -15,16 +15,51 @@ public struct Combinations<Base: Collection> {
1515
public let base: Base
1616

1717
@usableFromInline
18-
internal var k: Int
18+
internal let baseCount: Int
1919

20+
/// The range of accepted sizes of combinations.
21+
/// - Note: This may be `nil` if the attempted range entirely exceeds the
22+
/// upper bounds of the size of the `base` collection.
23+
@usableFromInline
24+
internal let kRange: Range<Int>?
25+
26+
/// Initializes a `Combinations` for all combinations of `base` of size `k`.
27+
/// - Parameters:
28+
/// - base: The collection to iterate over for combinations.
29+
/// - k: The expected size of each combination.
2030
@usableFromInline
2131
internal init(_ base: Base, k: Int) {
32+
self.init(base, kRange: k...k)
33+
}
34+
35+
/// Initializes a `Combinations` for all combinations of `base` of sizes
36+
/// within a given range.
37+
/// - Parameters:
38+
/// - base: The collection to iterate over for combinations.
39+
/// - kRange: The range of accepted sizes of combinations.
40+
@usableFromInline
41+
internal init<R: RangeExpression>(
42+
_ base: Base, kRange: R
43+
) where R.Bound == Int {
44+
let range = kRange.relative(to: 0 ..< .max)
2245
self.base = base
23-
self.k = base.count < k ? -1 : k
46+
let baseCount = base.count
47+
self.baseCount = baseCount
48+
let upperBound = baseCount + 1
49+
self.kRange = range.lowerBound < upperBound
50+
? range.clamped(to: 0 ..< upperBound)
51+
: nil
2452
}
25-
53+
54+
/// The total number of combinations.
2655
@inlinable
2756
public var count: Int {
57+
guard let k = self.kRange else { return 0 }
58+
let n = baseCount
59+
if k == 0 ..< (n + 1) {
60+
return 1 << n
61+
}
62+
2863
func binomial(n: Int, k: Int) -> Int {
2964
switch k {
3065
case n, 0: return 1
@@ -34,9 +69,9 @@ public struct Combinations<Base: Collection> {
3469
}
3570
}
3671

37-
return k >= 0
38-
? binomial(n: base.count, k: k)
39-
: 0
72+
return k.map {
73+
binomial(n: n, k: $0)
74+
}.reduce(0, +)
4075
}
4176
}
4277

@@ -46,18 +81,26 @@ extension Combinations: Sequence {
4681
@usableFromInline
4782
internal let base: Base
4883

84+
/// The current range of accepted sizes of combinations.
85+
/// - Note: The range is contracted until empty while iterating over
86+
/// combinations of different sizes. When the range is empty, iteration is
87+
/// finished.
4988
@usableFromInline
50-
internal var indexes: [Base.Index]
89+
internal var kRange: Range<Int>
5190

91+
/// Whether or not iteration is finished (`kRange` is empty)
5292
@usableFromInline
53-
internal var finished: Bool
93+
internal var isFinished: Bool {
94+
return kRange.isEmpty
95+
}
96+
97+
@usableFromInline
98+
internal var indexes: [Base.Index]
5499

55100
internal init(_ combinations: Combinations) {
56101
self.base = combinations.base
57-
self.finished = combinations.k < 0
58-
self.indexes = combinations.k < 0
59-
? []
60-
: Array(combinations.base.indices.prefix(combinations.k))
102+
self.kRange = combinations.kRange ?? 0..<0
103+
self.indexes = Array(combinations.base.indices.prefix(kRange.lowerBound))
61104
}
62105

63106
/// Advances the current indices to the next set of combinations. If
@@ -80,24 +123,35 @@ extension Combinations: Sequence {
80123
/// // so the iteration is finished.
81124
@usableFromInline
82125
internal mutating func advance() {
126+
/// Advances `kRange` by incrementing its `lowerBound` until the range is
127+
/// empty, when iteration is finished.
128+
func advanceKRange() {
129+
if kRange.lowerBound < kRange.upperBound {
130+
let advancedLowerBound = kRange.lowerBound + 1
131+
kRange = advancedLowerBound ..< kRange.upperBound
132+
indexes.removeAll(keepingCapacity: true)
133+
indexes.append(contentsOf: base.indices.prefix(kRange.lowerBound))
134+
}
135+
}
136+
83137
guard !indexes.isEmpty else {
84138
// Initial state for combinations of 0 elements is an empty array with
85139
// `finished == false`. Even though no indexes are involved, advancing
86140
// from that state means we are finished with iterating.
87-
finished = true
141+
advanceKRange()
88142
return
89143
}
90-
144+
91145
let i = indexes.count - 1
92146
base.formIndex(after: &indexes[i])
93147
if indexes[i] != base.endIndex { return }
94-
148+
95149
var j = i
96150
while indexes[i] == base.endIndex {
97151
j -= 1
98152
guard j >= 0 else {
99-
// Finished iterating over combinations
100-
finished = true
153+
// Finished iterating over combinations of this size.
154+
advanceKRange()
101155
return
102156
}
103157

@@ -113,7 +167,7 @@ extension Combinations: Sequence {
113167

114168
@inlinable
115169
public mutating func next() -> [Base.Element]? {
116-
if finished { return nil }
170+
guard !isFinished else { return nil }
117171
defer { advance() }
118172
return indexes.map { i in base[i] }
119173
}
@@ -129,10 +183,78 @@ extension Combinations: Equatable where Base: Equatable {}
129183
extension Combinations: Hashable where Base: Hashable {}
130184

131185
//===----------------------------------------------------------------------===//
132-
// combinations(count:)
186+
// combinations(ofCount:)
133187
//===----------------------------------------------------------------------===//
134188

135189
extension Collection {
190+
/// Returns a collection of combinations of this collection's elements, with
191+
/// each combination having the specified number of elements.
192+
///
193+
/// This example prints the different combinations of 1 and 2 from an array of
194+
/// four colors:
195+
///
196+
/// let colors = ["fuchsia", "cyan", "mauve", "magenta"]
197+
/// for combo in colors.combinations(ofCount: 1...2) {
198+
/// print(combo.joined(separator: ", "))
199+
/// }
200+
/// // fuchsia
201+
/// // cyan
202+
/// // mauve
203+
/// // magenta
204+
/// // fuchsia, cyan
205+
/// // fuchsia, mauve
206+
/// // fuchsia, magenta
207+
/// // cyan, mauve
208+
/// // cyan, magenta
209+
/// // mauve, magenta
210+
///
211+
/// The returned collection presents combinations in a consistent order, where
212+
/// the indices in each combination are in ascending lexicographical order.
213+
/// That is, in the example above, the combinations in order are the elements
214+
/// at `[0]`, `[1]`, `[2]`, `[3]`, `[0, 1]`, `[0, 2]`, `[0, 3]`, `[1, 2]`,
215+
/// `[1, 3]`, and finally `[2, 3]`.
216+
///
217+
/// This example prints _all_ the combinations (including an empty array and
218+
/// the original collection) from an array of numbers:
219+
///
220+
/// let numbers = [10, 20, 30, 40]
221+
/// for combo in numbers.combinations(ofCount: 0...) {
222+
/// print(combo)
223+
/// }
224+
/// // []
225+
/// // [10]
226+
/// // [20]
227+
/// // [30]
228+
/// // [40]
229+
/// // [10, 20]
230+
/// // [10, 30]
231+
/// // [10, 40]
232+
/// // [20, 30]
233+
/// // [20, 40]
234+
/// // [30, 40]
235+
/// // [10, 20, 30]
236+
/// // [10, 20, 40]
237+
/// // [10, 30, 40]
238+
/// // [20, 30, 40]
239+
/// // [10, 20, 30, 40]
240+
///
241+
/// If `kRange` is `0...0`, the resulting sequence has exactly one element, an
242+
/// empty array. The given range is limited to `0...base.count`. If the
243+
/// limited range is empty, the resulting sequence has no elements.
244+
///
245+
/// - Parameter kRange: The range of numbers of elements to include in each
246+
/// combination.
247+
///
248+
/// - Complexity: O(1) for random-access base collections. O(*n*) where *n*
249+
/// is the number of elements in the base collection, since `Combinations`
250+
/// accesses the `count` of the base collection.
251+
@inlinable
252+
public func combinations<R: RangeExpression>(
253+
ofCount kRange: R
254+
) -> Combinations<Self> where R.Bound == Int {
255+
return Combinations(self, kRange: kRange)
256+
}
257+
136258
/// Returns a collection of combinations of this collection's elements, with
137259
/// each combination having the specified number of elements.
138260
///
@@ -159,7 +281,9 @@ extension Collection {
159281
///
160282
/// - Parameter k: The number of elements to include in each combination.
161283
///
162-
/// - Complexity: O(1)
284+
/// - Complexity: O(1) for random-access base collections. O(*n*) where *n*
285+
/// is the number of elements in the base collection, since `Combinations`
286+
/// accesses the `count` of the base collection.
163287
@inlinable
164288
public func combinations(ofCount k: Int) -> Combinations<Self> {
165289
assert(k >= 0, "Can't have combinations with a negative number of elements.")

0 commit comments

Comments
 (0)