UInt64 { 0 }
107 | }
108 | var zero = ZeroGenerator()
109 | _ = nextOffset(w: 1, using: &zero) // must not crash
110 |
111 | struct AlmostAllZeroGenerator: RandomNumberGenerator {
112 | private var forward: SplitMix64
113 | private var count: Int = 0
114 |
115 | init(seed: UInt64) {
116 | forward = SplitMix64(seed: seed)
117 | }
118 |
119 | mutating func next() -> UInt64 {
120 | defer { count &+= 1 }
121 | if count % 1000 == 0 { return forward.next() }
122 | return 0
123 | }
124 | }
125 |
126 | var almostAllZero = AlmostAllZeroGenerator(seed: 0)
127 | _ = s.randomSample(count: k, using: &almostAllZero) // must not crash
128 | almostAllZero = AlmostAllZeroGenerator(seed: 0)
129 | _ = c.randomSample(count: k, using: &almostAllZero) // must not crash
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/Guides/Partition.md:
--------------------------------------------------------------------------------
1 | # Partition
2 |
3 | [[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Partition.swift) |
4 | [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/PartitionTests.swift)]
5 |
6 | Methods for performing a stable partition on mutable collections, and for
7 | finding the partitioning index in an already partitioned collection.
8 |
9 | The standard library’s existing `partition(by:)` method, which re-orders the
10 | elements in a collection into two partitions based on a given predicate, doesn’t
11 | guarantee stability for either partition. That is, the order of the elements in
12 | each partition doesn’t necessarily match their relative order in the original
13 | collection. These new methods expand on the existing `partition(by:)` by
14 | providing stability for one or both partitions.
15 |
16 | ```swift
17 | // existing partition(by:) - unstable ordering
18 | var numbers = [10, 20, 30, 40, 50, 60, 70, 80]
19 | let p1 = numbers.partition(by: { $0.isMultiple(of: 20) })
20 | // p1 == 4
21 | // numbers == [10, 70, 30, 50, 40, 60, 20, 80]
22 |
23 | // new stablePartition(by:) - keeps the relative order of both partitions
24 | numbers = [10, 20, 30, 40, 50, 60, 70, 80]
25 | let p2 = numbers.stablePartition(by: { $0.isMultiple(of: 20) })
26 | // p2 == 4
27 | // numbers == [10, 30, 50, 70, 20, 40, 60, 80]
28 | ```
29 |
30 | Since partitioning is frequently used in divide-and-conquer algorithms, we also
31 | include a variant that accepts a range parameter to avoid copying when mutating
32 | slices, as well as a range-based variant of the existing standard library
33 | partition.
34 |
35 | The `partitioningIndex(where:)` method returns the index of the start of the
36 | second partition when called on an already partitioned collection.
37 |
38 | ```swift
39 | let numbers = [10, 30, 50, 70, 20, 40, 60]
40 | let p = numbers.partitioningIndex(where: { $0.isMultiple(of: 20) })
41 | // numbers[.. Bool
68 | ) rethrows -> Index
69 |
70 | mutating func stablePartition(
71 | subrange: Range,
72 | by belongsInSecondPartition: (Element) throws -> Bool
73 | ) rethrows -> Index
74 |
75 | mutating func partition(
76 | subrange: Range,
77 | by belongsInSecondPartition: (Element) throws -> Bool
78 | ) rethrows -> Index
79 | }
80 |
81 | extension Collection {
82 | func partitioningIndex(
83 | where belongsInSecondPartition: (Element) throws -> Bool
84 | ) rethrows -> Index
85 | }
86 |
87 | extension Sequence {
88 | public func partitioned(
89 | by predicate: (Element) throws -> Bool
90 | ) rethrows -> (falseElements: [Element], trueElements: [Element])
91 | }
92 | ```
93 |
94 | ### Complexity
95 |
96 | The existing partition is an O(_n_) operation, where _n_ is the length of the
97 | range to be partitioned, while the stable partition is O(_n_ log _n_). Both
98 | partitions have algorithms with improved performance for bidirectional
99 | collections, so it would be ideal for those to be customization points were they
100 | to eventually land in the standard library.
101 |
102 | `partitioningIndex(where:)` is a slight generalization of a binary search, and
103 | is an O(log _n_) operation for random-access collections; O(_n_) otherwise.
104 |
105 | `partitioned(by:)` is an O(_n_) operation, where _n_ is the number of elements
106 | in the original sequence.
107 |
108 | ### Comparison with other languages
109 |
110 | **C++:** The `` library defines `partition`, `stable_partition`, and
111 | `partition_point` functions with similar semantics to these. Notably, in the C++
112 | implementation, the result of partitioning has the opposite polarity, with the
113 | passing elements in the first partition and failing elements in the second.
114 |
115 | **Rust:** Rust includes the `partition` method, which returns separate
116 | collections of passing and failing elements, and `partition_in_place`, which
117 | matches the Swift standard library’s existing `partition(by:)` method.
118 |
--------------------------------------------------------------------------------
/Guides/Permutations.md:
--------------------------------------------------------------------------------
1 | # Permutations
2 |
3 | [[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Permutations.swift) |
4 | [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/PermutationsTests.swift)]
5 |
6 | Methods that compute permutations of a collection’s elements, or of a subset of
7 | those elements.
8 |
9 | The `permutations(ofCount:)` method, when called without the `ofCount`
10 | parameter, returns a sequence of all the different permutations of a
11 | collection’s elements:
12 |
13 | ```swift
14 | let numbers = [10, 20, 30]
15 | for perm in numbers.permutations() {
16 | print(perm)
17 | }
18 | // [10, 20, 30]
19 | // [10, 30, 20]
20 | // [20, 10, 30]
21 | // [20, 30, 10]
22 | // [30, 10, 20]
23 | // [30, 20, 10]
24 | ```
25 |
26 | Passing a value for `ofCount` generates partial permutations, each with the
27 | specified number of elements:
28 |
29 | ```swift
30 | for perm in numbers.permutations(ofCount: 2) {
31 | print(perm)
32 | }
33 | // [10, 20]
34 | // [10, 30]
35 | // [20, 10]
36 | // [20, 30]
37 | // [30, 10]
38 | // [30, 20]
39 | ```
40 |
41 | The permutations or partial permutations are generated in increasing
42 | lexicographic order of the collection’s original ordering (rather than the order
43 | of the elements themselves). The first permutation will always consist of
44 | elements in their original order, and the last will have the elements in the
45 | reverse of their original order.
46 |
47 | Values that are repeated in the original collection are always treated as
48 | separate values in the resulting permutations:
49 |
50 | ```swift
51 | let numbers2 = [20, 10, 10]
52 | for perm in numbers2.permutations() {
53 | print(perm)
54 | }
55 | // [20, 10, 10]
56 | // [20, 10, 10]
57 | // [10, 20, 10]
58 | // [10, 10, 20]
59 | // [10, 20, 10]
60 | // [10, 10, 20]
61 | ```
62 |
63 | To generate only unique permutations, use the `uniquePermutations(ofCount:)` method:
64 |
65 | ```swift
66 | for perm in numbers2.uniquePermutations() {
67 | print(perm)
68 | }
69 | // [20, 10, 10]
70 | // [10, 20, 10]
71 | // [10, 10, 20]
72 | ```
73 |
74 | Given a range, the methods return a sequence of all the different permutations of the given sizes of a collection’s elements in increasing order of size.
75 |
76 | ```swift
77 | let numbers = [10, 20, 30]
78 | for perm in numbers.permutations(ofCount: 0...) {
79 | print(perm)
80 | }
81 | // []
82 | // [10]
83 | // [20]
84 | // [30]
85 | // [10, 20]
86 | // [10, 30]
87 | // [20, 10]
88 | // [20, 30]
89 | // [30, 10]
90 | // [30, 20]
91 | // [10, 20, 30]
92 | // [10, 30, 20]
93 | // [20, 10, 30]
94 | // [20, 30, 10]
95 | // [30, 10, 20]
96 | // [30, 20, 10]
97 | ```
98 |
99 | ## Detailed Design
100 |
101 | The `permutations(ofCount:)` and `uniquePermutations(ofCount:)` methods are
102 | declared as `Collection` extensions, and return `PermutationsSequence` and
103 | `UniquePermutationsSequence` instances, respectively:
104 |
105 | ```swift
106 | extension Collection {
107 | public func permutations(ofCount k: Int? = nil) -> PermutationsSequence
108 | public func permutations(ofCount kRange: R) -> PermutationsSequence
109 | where R: RangeExpression, R.Bound == Int
110 | }
111 |
112 | extension Collection where Element: Hashable {
113 | public func uniquePermutations(ofCount k: Int? = nil) -> UniquePermutationsSequence
114 | public func uniquePermutations(ofCount kRange: R) -> UniquePermutationsSequence
115 | where R: RangeExpression, R.Bound == Int
116 | }
117 | ```
118 |
119 | Since both result types need to store an array of the collection’s
120 | indices and mutate the array to generate each permutation, they only
121 | have `Sequence` conformance. Adding `Collection` conformance would require
122 | storing the array in the index type, which would in turn lead to copying the
123 | array at every index advancement. The `PermutationsSequence` and
124 | `UniquePermutationsSequence` types conforms to `LazySequenceProtocol` when their
125 | base type conforms.
126 |
127 | ### Complexity
128 |
129 | Calling `permutations()` is an O(1) operation. Creating the iterator for a
130 | `PermutationsSequence` instance and each call to
131 | `PermutationsSequence.Iterator.next()` is an O(_n_) operation.
132 |
133 | Calling `uniquePermutations()` is an O(_n_) operation, because it preprocesses
134 | the collection to find duplicate elements. Creating the iterator for and each
135 | call to `next()` is also an O(_n_) operation.
136 |
137 | ### Naming
138 |
139 | See the ["Naming" section for `combinations(ofCount:)`](Combinations.md#naming) for detail.
140 |
141 | ### Comparison with other languages
142 |
143 | **C++:** The `` library defines a `next_permutation` function that
144 | advances an array of comparable values through their lexicographic orderings.
145 | This function is very similar to the `uniquePermutations(ofCount:)` method.
146 |
147 | **Rust/Ruby/Python:** Rust, Ruby, and Python all define functions with
148 | essentially the same semantics as the `permutations(ofCount:)` method
149 | described here.
150 |
--------------------------------------------------------------------------------
/Sources/Algorithms/Unique.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the Swift Algorithms open source project
4 | //
5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors
6 | // Licensed under Apache License v2.0 with Runtime Library Exception
7 | //
8 | // See https://swift.org/LICENSE.txt for license information
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | /// A sequence wrapper that leaves out duplicate elements of a base sequence.
13 | public struct UniquedSequence {
14 | /// The base collection.
15 | @usableFromInline
16 | internal let base: Base
17 |
18 | /// The projection function.
19 | @usableFromInline
20 | internal let projection: (Base.Element) -> Subject
21 |
22 | @usableFromInline
23 | internal init(base: Base, projection: @escaping (Base.Element) -> Subject) {
24 | self.base = base
25 | self.projection = projection
26 | }
27 | }
28 |
29 | extension UniquedSequence: Sequence {
30 | /// The iterator for a `UniquedSequence` instance.
31 | public struct Iterator: IteratorProtocol {
32 | @usableFromInline
33 | internal var base: Base.Iterator
34 |
35 | @usableFromInline
36 | internal let projection: (Base.Element) -> Subject
37 |
38 | @usableFromInline
39 | internal var seen: Set = []
40 |
41 | @usableFromInline
42 | internal init(
43 | base: Base.Iterator,
44 | projection: @escaping (Base.Element) -> Subject
45 | ) {
46 | self.base = base
47 | self.projection = projection
48 | }
49 |
50 | @inlinable
51 | public mutating func next() -> Base.Element? {
52 | while let element = base.next() {
53 | if seen.insert(projection(element)).inserted {
54 | return element
55 | }
56 | }
57 | return nil
58 | }
59 | }
60 |
61 | @inlinable
62 | public func makeIterator() -> Iterator {
63 | Iterator(base: base.makeIterator(), projection: projection)
64 | }
65 | }
66 |
67 | extension UniquedSequence: LazySequenceProtocol
68 | where Base: LazySequenceProtocol {}
69 |
70 | //===----------------------------------------------------------------------===//
71 | // uniqued()
72 | //===----------------------------------------------------------------------===//
73 |
74 | extension Sequence where Element: Hashable {
75 | /// Returns a sequence with only the unique elements of this sequence, in the
76 | /// order of the first occurrence of each unique element.
77 | ///
78 | /// let animals = ["dog", "pig", "cat", "ox", "dog", "cat"]
79 | /// let uniqued = animals.uniqued()
80 | /// print(Array(uniqued))
81 | /// // Prints '["dog", "pig", "cat", "ox"]'
82 | ///
83 | /// - Returns: A sequence with only the unique elements of this sequence.
84 | /// .
85 | /// - Complexity: O(1).
86 | @inlinable
87 | public func uniqued() -> UniquedSequence {
88 | UniquedSequence(base: self, projection: { $0 })
89 | }
90 | }
91 |
92 | extension Sequence {
93 | /// Returns an array with the unique elements of this sequence (as determined
94 | /// by the given projection), in the order of the first occurrence of each
95 | /// unique element.
96 | ///
97 | /// This example finds the elements of the `animals` array with unique
98 | /// first characters:
99 | ///
100 | /// let animals = ["dog", "pig", "cat", "ox", "cow", "owl"]
101 | /// let uniqued = animals.uniqued(on: { $0.first })
102 | /// print(uniqued)
103 | /// // Prints '["dog", "pig", "cat", "ox"]'
104 | ///
105 | /// - Parameter projection: A closure that transforms an element into the
106 | /// value to use for uniqueness. If `projection` returns the same value for
107 | /// two different elements, the second element will be excluded from the
108 | /// resulting array.
109 | ///
110 | /// - Returns: An array with only the unique elements of this sequence, as
111 | /// determined by the result of `projection` for each element.
112 | ///
113 | /// - Complexity: O(*n*), where *n* is the length of the sequence.
114 | @inlinable
115 | public func uniqued(
116 | on projection: (Element) throws -> Subject
117 | ) rethrows -> [Element] {
118 | var seen: Set = []
119 | var result: [Element] = []
120 | for element in self {
121 | if seen.insert(try projection(element)).inserted {
122 | result.append(element)
123 | }
124 | }
125 | return result
126 | }
127 | }
128 |
129 | //===----------------------------------------------------------------------===//
130 | // lazy.uniqued()
131 | //===----------------------------------------------------------------------===//
132 |
133 | extension LazySequenceProtocol {
134 | /// Returns a lazy sequence with the unique elements of this sequence (as
135 | /// determined by the given projection), in the order of the first occurrence
136 | /// of each unique element.
137 | ///
138 | /// - Complexity: O(1).
139 | @inlinable
140 | public func uniqued(
141 | on projection: @escaping (Element) -> Subject
142 | ) -> UniquedSequence {
143 | UniquedSequence(base: self, projection: projection)
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/Tests/SwiftAlgorithmsTests/JoinedTests.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the Swift Algorithms open source project
4 | //
5 | // Copyright (c) 2021 Apple Inc. and the Swift project authors
6 | // Licensed under Apache License v2.0 with Runtime Library Exception
7 | //
8 | // See https://swift.org/LICENSE.txt for license information
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | import XCTest
13 |
14 | @testable import Algorithms
15 |
16 | final class JoinedTests: XCTestCase {
17 | let stringArrays = [
18 | [],
19 | [""],
20 | ["", ""],
21 | ["foo"],
22 | ["foo", "bar"],
23 | ["", "", "foo", "", "bar", "baz", ""],
24 | ]
25 |
26 | func testJoined() {
27 | let expected = ["", "", "", "foo", "foobar", "foobarbaz"]
28 |
29 | for (strings, expected) in zip(stringArrays, expected) {
30 | // regular sequence
31 | expectEqualSequences(AnySequence(strings).joined(), expected)
32 |
33 | // lazy sequence
34 | expectEqualSequences(AnySequence(strings).lazy.joined(), expected)
35 |
36 | // regular collection
37 | expectEqualSequences(strings.joined(), expected)
38 |
39 | // lazy collection
40 | expectEqualSequences(
41 | strings.lazy.joined() as FlattenCollection, expected)
42 | }
43 | }
44 |
45 | func testJoinedByElement() {
46 | let separator: Character = " "
47 | let expected = ["", "", " ", "foo", "foo bar", " foo bar baz "]
48 |
49 | for (strings, expected) in zip(stringArrays, expected) {
50 | expectEqualSequences(
51 | AnySequence(strings).joined(by: separator), expected)
52 | expectEqualSequences(
53 | AnySequence(strings).lazy.joined(by: separator), expected)
54 | expectEqualSequences(strings.joined(by: separator), expected)
55 | expectEqualSequences(strings.lazy.joined(by: separator), expected)
56 | }
57 | }
58 |
59 | func testJoinedBySequence() {
60 | let separator = ", "
61 | let expected = ["", "", ", ", "foo", "foo, bar", ", , foo, , bar, baz, "]
62 |
63 | for (strings, expected) in zip(stringArrays, expected) {
64 | expectEqualSequences(
65 | AnySequence(strings).joined(by: separator), expected)
66 | expectEqualSequences(
67 | AnySequence(strings).lazy.joined(by: separator), expected)
68 | expectEqualSequences(strings.joined(by: separator), expected)
69 | expectEqualSequences(strings.lazy.joined(by: separator), expected)
70 | }
71 | }
72 |
73 | func testJoinedByElementClosure() {
74 | let separator = { (left: String, right: String) -> Character in
75 | left.isEmpty || right.isEmpty ? " " : "-"
76 | }
77 |
78 | let expected = ["", "", " ", "foo", "foo-bar", " foo bar-baz "]
79 |
80 | for (strings, expected) in zip(stringArrays, expected) {
81 | expectEqualSequences(
82 | AnySequence(strings).joined(by: separator), expected)
83 | expectEqualSequences(
84 | AnySequence(strings).lazy.joined(by: separator), expected)
85 | expectEqualSequences(strings.joined(by: separator), expected)
86 | expectEqualSequences(strings.lazy.joined(by: separator), expected)
87 | }
88 | }
89 |
90 | func testJoinedBySequenceClosure() {
91 | let separator = { (left: String, right: String) in
92 | "(\(left.count), \(right.count))"
93 | }
94 |
95 | let expected = [
96 | "",
97 | "",
98 | "(0, 0)",
99 | "foo",
100 | "foo(3, 3)bar",
101 | "(0, 0)(0, 3)foo(3, 0)(0, 3)bar(3, 3)baz(3, 0)",
102 | ]
103 |
104 | for (strings, expected) in zip(stringArrays, expected) {
105 | expectEqualSequences(
106 | AnySequence(strings).joined(by: separator), expected)
107 | expectEqualSequences(
108 | AnySequence(strings).lazy.joined(by: separator), expected)
109 | expectEqualSequences(strings.joined(by: separator), expected)
110 | expectEqualSequences(strings.lazy.joined(by: separator), expected)
111 | }
112 | }
113 |
114 | func testJoinedLazy() {
115 | requireLazySequence(AnySequence([[1], [2]]).lazy.joined())
116 | requireLazySequence(AnySequence([[1], [2]]).lazy.joined(by: 1))
117 | requireLazySequence(
118 | AnySequence([[1], [2]]).lazy.joined(by: { _, _ in 1 }))
119 | requireLazyCollection([[1], [2]].lazy.joined())
120 | requireLazyCollection([[1], [2]].lazy.joined(by: 1))
121 | requireLazyCollection([[1], [2]].lazy.joined(by: { _, _ in 1 }))
122 | }
123 |
124 | func testJoinedIndexTraversals() {
125 | let validator = IndexValidator>()
126 |
127 | // the last test case takes too long to run
128 | for strings in stringArrays.dropLast() {
129 | validator.validate(strings.joined() as FlattenCollection)
130 | }
131 | }
132 |
133 | func testJoinedByIndexTraversals() {
134 | let validator1 = IndexValidator>()
135 | let validator2 = IndexValidator<
136 | JoinedByClosureCollection<[String], String>
137 | >()
138 |
139 | // the last test case takes too long to run
140 | for (strings, separator) in product(
141 | stringArrays.dropLast(), ["", " ", ", "])
142 | {
143 | validator1.validate(strings.joined(by: separator))
144 | validator2.validate(strings.lazy.joined(by: { _, _ in separator }))
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/Tests/SwiftAlgorithmsTests/StrideTests.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the Swift Algorithms open source project
4 | //
5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors
6 | // Licensed under Apache License v2.0 with Runtime Library Exception
7 | //
8 | // See https://swift.org/LICENSE.txt for license information
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | import Algorithms
13 | import XCTest
14 |
15 | final class StridingTests: XCTestCase {
16 |
17 | func testStride() {
18 | let a = 0...10
19 | expectEqualSequences(a.striding(by: 1), (0...10))
20 | expectEqualSequences(a.striding(by: 2), [0, 2, 4, 6, 8, 10])
21 | expectEqualSequences(a.striding(by: 3), [0, 3, 6, 9])
22 | expectEqualSequences(a.striding(by: 4), [0, 4, 8])
23 | expectEqualSequences(a.striding(by: 5), [0, 5, 10])
24 | expectEqualSequences(a.striding(by: 10), [0, 10])
25 | expectEqualSequences(a.striding(by: 11), [0])
26 |
27 | let s = (0...).prefix(11)
28 | expectEqualSequences(s.striding(by: 1), (0...10))
29 | expectEqualSequences(s.striding(by: 2), [0, 2, 4, 6, 8, 10])
30 | expectEqualSequences(s.striding(by: 3), [0, 3, 6, 9])
31 | expectEqualSequences(s.striding(by: 4), [0, 4, 8])
32 | expectEqualSequences(s.striding(by: 5), [0, 5, 10])
33 | expectEqualSequences(s.striding(by: 10), [0, 10])
34 | expectEqualSequences(s.striding(by: 11), [0])
35 |
36 | let empty = (0...).prefix(0)
37 | expectEqualSequences(empty.striding(by: 2), [])
38 | }
39 |
40 | func testStrideString() {
41 | let s = "swift"
42 | expectEqualSequences(s.striding(by: 2), ["s", "i", "t"])
43 | }
44 |
45 | func testStrideReversed() {
46 | let a = [0, 1, 2, 3, 4, 5]
47 | expectEqualSequences(a.striding(by: 3).reversed(), [3, 0])
48 | expectEqualSequences(a.reversed().striding(by: 2), [5, 3, 1])
49 | }
50 |
51 | func testStrideIndexes() {
52 | let a = [0, 1, 2, 3, 4, 5].striding(by: 2)
53 | var i = a.startIndex
54 | XCTAssertEqual(a[i], 0)
55 | a.formIndex(after: &i)
56 | XCTAssertEqual(a[i], 2)
57 | a.formIndex(after: &i)
58 | XCTAssertEqual(a[i], 4)
59 | a.formIndex(before: &i)
60 | XCTAssertEqual(a[i], 2)
61 | a.formIndex(before: &i)
62 | XCTAssertEqual(a[i], 0)
63 | // a.formIndex(before: &i) // Precondition failed: Incrementing past start index
64 | // a.index(after: a.endIndex) // Precondition failed: Advancing past end index
65 | }
66 |
67 | func testStrideLast() {
68 | XCTAssertEqual((1...10).striding(by: 2).last, 9) // 1, 3, 5, 7, 9
69 | XCTAssertEqual((1...10).striding(by: 3).last, 10) // 1, 4, 7, 10
70 | XCTAssertEqual((1...10).striding(by: 4).last, 9) // 1, 5, 9
71 | XCTAssertEqual((1...10).striding(by: 5).last, 6) // 1, 6
72 | XCTAssertEqual((1...100).striding(by: 50).last, 51) // 1, 51
73 | XCTAssertEqual((1...5).striding(by: 2).last, 5) // 1, 3, 5
74 | XCTAssertEqual([Int]().striding(by: 2).last, nil) // empty
75 | }
76 |
77 | func testCount() {
78 | let empty = [Int]().striding(by: 2)
79 | XCTAssertEqual(empty.count, 0)
80 | let a = (0...10)
81 | XCTAssertEqual(a.striding(by: 1).count, (0...10).count)
82 | XCTAssertEqual(a.striding(by: 2).count, [0, 2, 4, 6, 8, 10].count)
83 | XCTAssertEqual(a.striding(by: 3).count, [0, 3, 6, 9].count)
84 | XCTAssertEqual(a.striding(by: 4).count, [0, 4, 8].count)
85 | XCTAssertEqual(a.striding(by: 5).count, [0, 5, 10].count)
86 | XCTAssertEqual(a.striding(by: 10).count, [0, 10].count)
87 | XCTAssertEqual(a.striding(by: 11).count, [0].count)
88 | }
89 |
90 | func testIndexTraversals() {
91 | do {
92 | let empty: [Int] = []
93 | let validator = IndexValidator>()
94 | validator.validate(empty.striding(by: 1))
95 | validator.validate(empty.striding(by: 2))
96 | }
97 |
98 | do {
99 | let range = 0...100
100 | let validator = IndexValidator>>()
101 | validator.validate(range.striding(by: 10))
102 | validator.validate(range.striding(by: 11))
103 | validator.validate(range.striding(by: 101))
104 | }
105 |
106 | do {
107 | let array = Array(0...100)
108 | let validator = IndexValidator>()
109 | validator.validate(array.striding(by: 10))
110 | validator.validate(array.striding(by: 11))
111 | validator.validate(array.striding(by: 101))
112 | }
113 |
114 | do {
115 | let string = "swift rocks"
116 | let validator = IndexValidator>()
117 | validator.validate(string.striding(by: 1))
118 | validator.validate(string.striding(by: 2))
119 | validator.validate(string.striding(by: 10))
120 | }
121 | }
122 |
123 | func testOffsetBy() {
124 | let a = (0...100).striding(by: 22)
125 | let b = [0, 22, 44, 66, 88]
126 | for i in 0..(
31 | _ initial: Result,
32 | _ transform: @escaping (Result, Element) -> Result
33 | ) -> ExclusiveReductionsSequence
34 |
35 | public func reductions(
36 | into initial: Result,
37 | _ transform: @escaping (inout Result, Element) -> Void
38 | ) -> ExclusiveReductionsSequence
39 |
40 | public func reductions(
41 | _ transform: @escaping (Element, Element) -> Element
42 | ) -> InclusiveReductionsSequence
43 | }
44 | ```
45 |
46 | ```swift
47 | extension Sequence {
48 | public func reductions(
49 | _ initial: Result,
50 | _ transform: (Result, Element) throws -> Result
51 | ) rethrows -> [Result]
52 |
53 | public func reductions(
54 | into initial: Result,
55 | _ transform: (inout Result, Element) throws -> Void
56 | ) rethrows -> [Result]
57 |
58 | public func reductions(
59 | _ transform: (Element, Element) throws -> Element
60 | ) rethrows -> [Element]
61 | }
62 | ```
63 |
64 | ### Complexity
65 |
66 | Calling the lazy methods, those defined on `LazySequenceProtocol`, is O(_1_).
67 | Calling the eager methods, those returning an array, is O(_n_).
68 |
69 | ### Naming
70 |
71 | While the name `scan` is the term of art for this function, it has been
72 | discussed that `reductions` aligns better with the existing `reduce` function
73 | and will aid newcomers that might not know the existing `scan` term.
74 |
75 | Deprecated `scan` methods have been added for people who are familiar with the
76 | term, so they can easily discover the `reductions` methods via compiler
77 | deprecation warnings.
78 |
79 | Below are two quotes from the Swift forum [discussion about SE-0045][SE-0045]
80 | which proposed adding `scan` to the standard library and one from
81 | [issue #25][Issue 25] on the swift-algorithms GitHub project. These provide
82 | the reasoning to use the name `reductions`.
83 |
84 | [Brent Royal-Gordon][Brent_Royal-Gordon]:
85 | > I really like the `reduce`/`reductions` pairing instead of `reduce`/`scan`;
86 | it does a really good job of explaining the relationship between the two
87 | functions.
88 |
89 | [David Rönnqvist][David Rönnqvist]:
90 | > As other have already pointed out, I also feel that `scan` is the least
91 | intuitive name among these and that the `reduce`/`reductions` pairing would do
92 | a good job at explaining the relation between the two.
93 |
94 | [Kyle Macomber][Kyle Macomber]:
95 | > As someone unfamiliar with the prior art, `reductions` strikes me as very
96 | approachable—I feel like I can extrapolate the expected behavior purely from my
97 | familiarity with `reduce`.
98 |
99 | As part of early discussions, it was decided to have two variants, one which
100 | takes an initial value to use for the first element in the returned sequence,
101 | and another which uses the first value of the base sequence as the initial
102 | value. C++ calls these variants exclusive and inclusive respectively and so
103 | these terms carry through as the name for the lazy sequences;
104 | `ExclusiveReductionsSequence` and `InclusiveReductionsSequence`.
105 |
106 | [SE-0045]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382
107 | [Issue 25]: https://github.com/apple/swift-algorithms/issues/25
108 | [Brent_Royal-Gordon]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/6
109 | [David Rönnqvist]: https://forums.swift.org/t/review-se-0045-add-scan-prefix-while-drop-while-and-iterate-to-the-stdlib/2382/8
110 | [Kyle Macomber]: https://github.com/apple/swift-algorithms/issues/25#issuecomment-709317894
111 |
112 | ### Comparison with other languages
113 |
114 | **C++:** As of C++17, the `` library includes both
115 | [`exclusive_scan`][C++ Exclusive] and [`inclusive_scan`][C++ Inclusive]
116 | functions.
117 |
118 | **[Clojure][Clojure]:** Clojure 1.2 added a `reductions` function.
119 |
120 | **[Haskell][Haskell]:** Haskell includes a `scan` function for its
121 | `Traversable` type, which is akin to Swift's `Sequence`.
122 |
123 | **Python:** Python’s `itertools` includes an `accumulate` method. In version
124 | 3.3, a function parameter was added. Version 3.8 added the optional initial
125 | parameter.
126 |
127 | **[Rust][Rust]:** Rust provides a `scan` function.
128 |
129 | [C++ Exclusive]: https://en.cppreference.com/w/cpp/algorithm/exclusive_scan
130 | [C++ Inclusive]: https://en.cppreference.com/w/cpp/algorithm/inclusive_scan
131 | [Clojure]: http://clojure.github.io/clojure/clojure.core-api.html#clojure.core/reductions
132 | [Haskell]: http://hackage.haskell.org/package/base-4.8.2.0/docs/Prelude.html#v:scanl
133 | [Rust]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.scan
134 |
--------------------------------------------------------------------------------
/Sources/Algorithms/Compacted.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the Swift Algorithms open source project
4 | //
5 | // Copyright (c) 2021 Apple Inc. and the Swift project authors
6 | // Licensed under Apache License v2.0 with Runtime Library Exception
7 | //
8 | // See https://swift.org/LICENSE.txt for license information
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | /// A `Sequence` that iterates over every non-nil element from the original
13 | /// `Sequence`.
14 | public struct CompactedSequence: Sequence
15 | where Base.Element == Element? {
16 |
17 | @usableFromInline
18 | let base: Base
19 |
20 | @inlinable
21 | init(base: Base) {
22 | self.base = base
23 | }
24 |
25 | public struct Iterator: IteratorProtocol {
26 | @usableFromInline
27 | var base: Base.Iterator
28 |
29 | @inlinable
30 | init(base: Base.Iterator) {
31 | self.base = base
32 | }
33 |
34 | @inlinable
35 | public mutating func next() -> Element? {
36 | while let wrapped = base.next() {
37 | guard let some = wrapped else { continue }
38 | return some
39 | }
40 | return nil
41 | }
42 | }
43 |
44 | @inlinable
45 | public func makeIterator() -> Iterator {
46 | Iterator(base: base.makeIterator())
47 | }
48 | }
49 |
50 | extension Sequence {
51 | /// Returns a new `Sequence` that iterates over every non-nil element from the
52 | /// original `Sequence`.
53 | ///
54 | /// Produces the same result as `c.compactMap { $0 }`.
55 | ///
56 | /// let c = [1, nil, 2, 3, nil]
57 | /// for num in c.compacted() {
58 | /// print(num)
59 | /// }
60 | /// // 1
61 | /// // 2
62 | /// // 3
63 | ///
64 | /// - Returns: A `Sequence` where the element is the unwrapped original
65 | /// element and iterates over every non-nil element from the original
66 | /// `Sequence`.
67 | ///
68 | /// Complexity: O(1)
69 | @inlinable
70 | public func compacted() -> CompactedSequence
71 | where Element == Unwrapped? {
72 | CompactedSequence(base: self)
73 | }
74 | }
75 |
76 | /// A `Collection` that iterates over every non-nil element from the original
77 | /// `Collection`.
78 | public struct CompactedCollection: Collection
79 | where Base.Element == Element? {
80 |
81 | @usableFromInline
82 | let base: Base
83 |
84 | @inlinable
85 | init(base: Base) {
86 | self.base = base
87 | let idx = base.firstIndex(where: { $0 != nil }) ?? base.endIndex
88 | self.startIndex = Index(base: idx)
89 | }
90 |
91 | public struct Index {
92 | @usableFromInline
93 | let base: Base.Index
94 |
95 | @inlinable
96 | init(base: Base.Index) {
97 | self.base = base
98 | }
99 | }
100 |
101 | public var startIndex: Index
102 |
103 | @inlinable
104 | public var endIndex: Index { Index(base: base.endIndex) }
105 |
106 | @inlinable
107 | public subscript(position: Index) -> Element {
108 | // swift-format-ignore: NeverForceUnwrap
109 | // All indices are only for non-`nil` elements.
110 | base[position.base]!
111 | }
112 |
113 | @inlinable
114 | public func index(after i: Index) -> Index {
115 | precondition(i != endIndex, "Index out of bounds")
116 |
117 | let baseIdx = base.index(after: i.base)
118 | guard let idx = base[baseIdx...].firstIndex(where: { $0 != nil })
119 | else { return endIndex }
120 | return Index(base: idx)
121 | }
122 | }
123 |
124 | extension CompactedCollection: BidirectionalCollection
125 | where Base: BidirectionalCollection {
126 |
127 | @inlinable
128 | public func index(before i: Index) -> Index {
129 | precondition(i != startIndex, "Index out of bounds")
130 |
131 | guard
132 | let idx =
133 | base[startIndex.base.. Bool {
146 | lhs.base < rhs.base
147 | }
148 | }
149 |
150 | extension CompactedCollection.Index: Hashable
151 | where Base.Index: Hashable {}
152 |
153 | extension Collection {
154 | /// Returns a new `Collection` that iterates over every non-nil element from
155 | /// the original `Collection`.
156 | ///
157 | /// Produces the same result as `c.compactMap { $0 }`.
158 | ///
159 | /// let c = [1, nil, 2, 3, nil]
160 | /// for num in c.compacted() {
161 | /// print(num)
162 | /// }
163 | /// // 1
164 | /// // 2
165 | /// // 3
166 | ///
167 | /// - Returns: A `Collection` where the element is the unwrapped original
168 | /// element and iterates over every non-nil element from the original
169 | /// `Collection`.
170 | ///
171 | /// Complexity: O(*n*) where *n* is the number of elements in the
172 | /// original `Collection`.
173 | @inlinable
174 | public func compacted() -> CompactedCollection
175 | where Element == Unwrapped? {
176 | CompactedCollection(base: self)
177 | }
178 | }
179 |
180 | //===----------------------------------------------------------------------===//
181 | // Protocol Conformances
182 | //===----------------------------------------------------------------------===//
183 |
184 | extension CompactedSequence: LazySequenceProtocol
185 | where Base: LazySequenceProtocol {}
186 |
187 | extension CompactedCollection: LazySequenceProtocol, LazyCollectionProtocol
188 | where Base: LazySequenceProtocol {}
189 |
--------------------------------------------------------------------------------
/Guides/Chunked.md:
--------------------------------------------------------------------------------
1 | # Chunked
2 |
3 | [[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Chunked.swift) |
4 | [Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/ChunkedTests.swift)]
5 |
6 | Break a collection into nonoverlapping subsequences:
7 |
8 | * `chunked(by:)` forms chunks of consecutive elements that pass a binary predicate,
9 | * `chunked(on:)` forms chunks of consecutive elements that project to equal values,
10 | * `chunks(ofCount:)` forms chunks of a given size, and
11 | * `evenlyChunked(in:)` forms a given number of equally-sized chunks.
12 |
13 | `chunked(by:)` uses a binary predicate to test consecutive elements, separating
14 | chunks where the predicate returns `false`. For example, you can chunk a
15 | collection into ascending sequences using this method:
16 |
17 | ```swift
18 | let numbers = [10, 20, 30, 10, 40, 40, 10, 20]
19 | let chunks = numbers.chunked(by: { $0 <= $1 })
20 | // [[10, 20, 30], [10, 40, 40], [10, 20]]
21 | ```
22 |
23 | The `chunked(on:)` method, by contrast, takes a projection of each element and
24 | separates chunks where the projection of two consecutive elements is not equal.
25 | The result includes both the projected value and the subsequence that groups
26 | elements with that projected value:
27 |
28 | ```swift
29 | let names = ["David", "Kyle", "Karoy", "Nate"]
30 | let chunks = names.chunked(on: \.first!)
31 | // [("D", ["David"]), ("K", ["Kyle", "Karoy"]), ("N", ["Nate"])]
32 | ```
33 |
34 | The `chunks(ofCount:)` method takes a `count` parameter (required to be > 0) and
35 | separates the collection into chunks of this given count. If the length of the
36 | collection is a multiple of the `count` parameter, all chunks will have the
37 | a count equal to the parameter. Otherwise, the last chunk will contain the remaining elements.
38 |
39 | ```swift
40 | let names = ["David", "Kyle", "Karoy", "Nate"]
41 | let evenly = names.chunks(ofCount: 2)
42 | // equivalent to [["David", "Kyle"], ["Karoy", "Nate"]]
43 |
44 | let remaining = names.chunks(ofCount: 3)
45 | // equivalent to [["David", "Kyle", "Karoy"], ["Nate"]]
46 | ```
47 |
48 | The `chunks(ofCount:)` method was previously [proposed](proposal) for inclusion
49 | in the standard library.
50 |
51 | The `evenlyChunked(in:)` method takes a `count` parameter and divides the
52 | collection into `count` number of equally-sized chunks. If the length of the
53 | collection is not a multiple of the `count` parameter, the chunks at the start
54 | will be longer than the chunks at the end.
55 |
56 | ```swift
57 | let evenChunks = (0..<15).evenlyChunked(in: 3)
58 | // equivalent to [0..<5, 5..<10, 10..<15]
59 |
60 | let nearlyEvenChunks = (0..<15).evenlyChunked(in: 4)
61 | // equivalent to [0..<4, 4..<8, 8..<12, 12..<15]
62 | ```
63 |
64 | When "chunking" a collection, the entire collection is included in the result,
65 | unlike the `split` family of methods, where separators are dropped.
66 | Joining the result of a chunking method call results in a collection equivalent
67 | to the original.
68 |
69 | ```swift
70 | c.elementsEqual(c.chunked(...).joined())
71 | // true
72 | ```
73 |
74 | [proposal]: https://github.com/apple/swift-evolution/pull/935
75 |
76 | ## Detailed Design
77 |
78 | The four methods are added to `Collection`, with matching versions of
79 | `chunked(by:)` and `chunked(on:)` that return a lazy wrapper added to
80 | `LazyCollectionProtocol`.
81 |
82 | ```swift
83 | extension Collection {
84 | public func chunked(
85 | by belongInSameGroup: (Element, Element) -> Bool
86 | ) -> [SubSequence]
87 |
88 | public func chunked(
89 | on projection: (Element) -> Subject
90 | ) -> [SubSequence]
91 |
92 | public func chunks(ofCount count: Int) -> ChunkedByCount
93 |
94 | public func evenlyChunked(in count: Int) -> EvenlyChunkedCollection
95 | }
96 |
97 | extension LazyCollectionProtocol {
98 | public func chunked(
99 | by belongInSameGroup: @escaping (Element, Element) -> Bool
100 | ) -> ChunkedByCollection
101 |
102 | public func chunked(
103 | on projection: @escaping (Element) -> Subject
104 | ) -> ChunkedOnCollection
105 | }
106 | ```
107 |
108 | Each of the "chunked" collection types are bidirectional when the wrapped
109 | collection is bidirectional. `ChunksOfCountCollection` and
110 | `EvenlyChunkedCollection` also conform to `RandomAccessCollection` and
111 | `LazySequenceProtocol` when their base collections conform.
112 |
113 | ### Complexity
114 |
115 | The eager methods are O(_n_) where _n_ is the number of elements in the
116 | collection. The lazy methods are O(_n_) because the start index is pre-computed.
117 |
118 | ### Naming
119 |
120 | The operation performed by these methods is similar to other ways of breaking a
121 | collection up into subsequences. In particular, the predicate-based
122 | `split(where:)` method looks similar to `chunked(on:)`. You can draw a
123 | distinction between these different operations based on the resulting
124 | subsequences:
125 |
126 | - `split`: *In the standard library.* Breaks a collection into subsequences,
127 | removing any elements that are considered "separators". The original collection
128 | cannot be recovered from the result of splitting.
129 | - `chunked`: *In this package.* Breaks a collection into subsequences,
130 | preserving each element in its initial ordering. Joining the resulting
131 | subsequences re-forms the original collection.
132 | - `sliced`: *Not included in this package or the stdlib.* Breaks a collection
133 | into potentially overlapping subsequences.
134 |
135 | ### Comparison with other languages
136 |
137 | **Ruby:** Ruby’s `Enumerable` class defines `chunk_while` and `chunk`, which map
138 | to the proposed `chunked(by:)` and `chunked(on:)` methods.
139 |
140 | **Rust:** Rust defines a variety of size-based `chunks` methods, of which the
141 | standard version corresponds to the `chunks(ofCount:)` method defined here.
142 |
--------------------------------------------------------------------------------
/Tests/SwiftAlgorithmsTests/CombinationsTests.swift:
--------------------------------------------------------------------------------
1 | //===----------------------------------------------------------------------===//
2 | //
3 | // This source file is part of the Swift Algorithms open source project
4 | //
5 | // Copyright (c) 2020-2021 Apple Inc. and the Swift project authors
6 | // Licensed under Apache License v2.0 with Runtime Library Exception
7 | //
8 | // See https://swift.org/LICENSE.txt for license information
9 | //
10 | //===----------------------------------------------------------------------===//
11 |
12 | import Algorithms
13 | import XCTest
14 |
15 | final class CombinationsTests: XCTestCase {
16 | func testCount() {
17 | let c = "ABCD"
18 |
19 | /// XCTAsserts that `x`'s `count` and `underestimatedCount` are both `l` at
20 | /// the given `file` and `line`.
21 | func check(
22 | _ x: CombinationsSequence, countsAre l: Int,
23 | file: StaticString, line: UInt
24 | ) {
25 | XCTAssertEqual(x.count, l, "unexpected count", file: file, line: line)
26 | XCTAssertEqual(
27 | x.underestimatedCount, l, "unexpected underestimatedCount",
28 | file: file, line: line)
29 | }
30 |
31 | /// XCTAsserts that the `count` and `underestimatedCount` of
32 | /// `c.combinations(ofCount: l)` are both `n` at the given `file` and
33 | /// `line`.
34 | func check(
35 | cHas n: Int,
36 | combinationsOfLength l: Int,
37 | file: StaticString = #filePath, line: UInt = #line
38 | ) {
39 | check(c.combinations(ofCount: l), countsAre: n, file: file, line: line)
40 | }
41 |
42 | /// XCTAsserts that the `count` and `underestimatedCount` of
43 | /// `c.combinations(ofCount: l)` are both `n` at the given `file` and
44 | /// `line`.
45 | func check(
46 | cHas n: Int,
47 | combinationsOfLengths l: R,
48 | file: StaticString = #filePath, line: UInt = #line
49 | ) where R.Bound == Int {
50 | check(c.combinations(ofCount: l), countsAre: n, file: file, line: line)
51 | }
52 |
53 | check(cHas: 1, combinationsOfLength: 0)
54 | check(cHas: 4, combinationsOfLength: 1)
55 | check(cHas: 6, combinationsOfLength: 2)
56 | check(cHas: 1, combinationsOfLength: 4)
57 |
58 | check(cHas: 1, combinationsOfLengths: 0...0)
59 | check(cHas: 4, combinationsOfLengths: 1...1)
60 | check(cHas: 10, combinationsOfLengths: 1...2)
61 | check(cHas: 14, combinationsOfLengths: 1...3)
62 | check(cHas: 11, combinationsOfLengths: 2...4)
63 |
64 | // `k` greater than element count results in same number of combinations
65 | check(cHas: 5, combinationsOfLengths: 3...10)
66 |
67 | // `k` greater than element count results in same number of combinations
68 | check(cHas: 1, combinationsOfLengths: 4...10)
69 |
70 | // `k` entirely greater than element count results in no combinations
71 | check(cHas: 0, combinationsOfLengths: 5...10)
72 |
73 | check(cHas: 16, combinationsOfLengths: 0...)
74 | check(cHas: 15, combinationsOfLengths: ...3)
75 | }
76 |
77 | func testCombinations() {
78 | let c = "ABCD"
79 |
80 | let c1 = c.combinations(ofCount: 1)
81 | XCTAssertEqual(["A", "B", "C", "D"], c1.map { String($0) })
82 |
83 | let c2 = c.combinations(ofCount: 2)
84 | XCTAssertEqual(["AB", "AC", "AD", "BC", "BD", "CD"], c2.map { String($0) })
85 |
86 | let c3 = c.combinations(ofCount: 3)
87 | XCTAssertEqual(["ABC", "ABD", "ACD", "BCD"], c3.map { String($0) })
88 |
89 | let c4 = c.combinations(ofCount: 4)
90 | XCTAssertEqual(["ABCD"], c4.map { String($0) })
91 |
92 | let c5 = c.combinations(ofCount: 2...4)
93 | XCTAssertEqual(
94 | ["AB", "AC", "AD", "BC", "BD", "CD", "ABC", "ABD", "ACD", "BCD", "ABCD"],
95 | c5.map { String($0) })
96 |
97 | let c6 = c.combinations(ofCount: 0...4)
98 | XCTAssertEqual(
99 | [
100 | "", "A", "B", "C", "D", "AB", "AC", "AD", "BC", "BD", "CD", "ABC",
101 | "ABD", "ACD", "BCD", "ABCD",
102 | ], c6.map { String($0) })
103 |
104 | let c7 = c.combinations(ofCount: 0...)
105 | XCTAssertEqual(
106 | [
107 | "", "A", "B", "C", "D", "AB", "AC", "AD", "BC", "BD", "CD", "ABC",
108 | "ABD", "ACD", "BCD", "ABCD",
109 | ], c7.map { String($0) })
110 |
111 | let c8 = c.combinations(ofCount: ...4)
112 | XCTAssertEqual(
113 | [
114 | "", "A", "B", "C", "D", "AB", "AC", "AD", "BC", "BD", "CD", "ABC",
115 | "ABD", "ACD", "BCD", "ABCD",
116 | ], c8.map { String($0) })
117 |
118 | let c9 = c.combinations(ofCount: ...3)
119 | XCTAssertEqual(
120 | [
121 | "", "A", "B", "C", "D", "AB", "AC", "AD", "BC", "BD", "CD", "ABC",
122 | "ABD", "ACD", "BCD",
123 | ], c9.map { String($0) })
124 |
125 | let c10 = c.combinations(ofCount: 1...)
126 | XCTAssertEqual(
127 | [
128 | "A", "B", "C", "D", "AB", "AC", "AD", "BC", "BD", "CD", "ABC", "ABD",
129 | "ACD", "BCD", "ABCD",
130 | ], c10.map { String($0) })
131 | }
132 |
133 | func testEmpty() {
134 | // `k == 0` results in one zero-length combination
135 | expectEqualSequences([[]], "".combinations(ofCount: 0))
136 | expectEqualSequences([[]], "".combinations(ofCount: 0...0))
137 | expectEqualSequences([[]], "ABCD".combinations(ofCount: 0))
138 | expectEqualSequences([[]], "ABCD".combinations(ofCount: 0...0))
139 |
140 | // `k` greater than element count results in zero combinations
141 | expectEqualSequences([], "".combinations(ofCount: 5))
142 | expectEqualSequences([], "".combinations(ofCount: 5...10))
143 | expectEqualSequences([], "ABCD".combinations(ofCount: 5))
144 | expectEqualSequences([], "ABCD".combinations(ofCount: 5...10))
145 | }
146 |
147 | func testCombinationsLazy() {
148 | requireLazySequence("ABC".lazy.combinations(ofCount: 1))
149 | requireLazySequence("ABC".lazy.combinations(ofCount: 1...3))
150 | requireLazySequence("ABC".lazy.combinations(ofCount: 1...))
151 | requireLazySequence("ABC".lazy.combinations(ofCount: ...3))
152 | requireLazySequence("ABC".lazy.combinations(ofCount: 0...))
153 | }
154 | }
155 |
--------------------------------------------------------------------------------