Skip to content

Commit bcdbac8

Browse files
authored
Add new FiniteCycle type as the return type of Collection.cycled(times:) (#106)
1 parent 718220d commit bcdbac8

File tree

4 files changed

+170
-17
lines changed

4 files changed

+170
-17
lines changed

Guides/Cycle.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ for x in (1...3).cycled(times: 3) {
1616
// Prints 1 through 3 three times
1717
```
1818

19-
`cycled(times:)` combines two other existing standard library functions
20-
(`repeatElement` and `joined`) to provide a more expressive way of repeating a
19+
`cycled(times:)` provides a more expressive way of repeating a
2120
collection's elements a limited number of times.
2221

2322
## Detailed Design
@@ -28,17 +27,18 @@ Two new methods are added to collections:
2827
extension Collection {
2928
func cycled() -> Cycle<Self>
3029

31-
func cycled(times: Int) -> FlattenSequence<Repeated<Self>>
30+
func cycled(times: Int) -> FiniteCycle<Self>
3231
}
3332
```
3433

3534
The new `Cycle` type is a sequence only, given that the `Collection` protocol
3635
design makes infinitely large types impossible/impractical. `Cycle` also
3736
conforms to `LazySequenceProtocol` when the base type conforms.
3837

39-
Note that despite its name, the returned `FlattenSequence` will always have
40-
`Collection` conformance, and will have `BidirectionalCollection` conformance
41-
when called on a bidirectional collection.
38+
Note that the returned `FiniteCycle` will always have `Collection`
39+
conformance, and will have `BidirectionalCollection` conformance
40+
when called on a bidirectional collection. `FiniteCycle` also
41+
conforms to `LazyCollectionProtocol` when the base type conforms.
4242

4343
### Complexity
4444

Sources/Algorithms/Cycle.swift

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,115 @@ extension Cycle: Sequence {
5757

5858
extension Cycle: LazySequenceProtocol where Base: LazySequenceProtocol {}
5959

60+
61+
/// A collection wrapper that repeats the elements of a base collection for a
62+
/// finite number of times.
63+
public struct FiniteCycle<Base: Collection> {
64+
/// A Product2 instance for iterating the Base collection.
65+
@usableFromInline
66+
internal let product: Product2<Range<Int>, Base>
67+
68+
@inlinable
69+
internal init(base: Base, times: Int) {
70+
self.product = Product2(0..<times, base)
71+
}
72+
}
73+
74+
extension FiniteCycle: LazySequenceProtocol, LazyCollectionProtocol
75+
where Base: LazyCollectionProtocol { }
76+
77+
extension FiniteCycle: Collection {
78+
79+
public typealias Element = Base.Element
80+
81+
public struct Index: Comparable {
82+
/// The index corresponding to the Product2 index at this position.
83+
@usableFromInline
84+
internal let productIndex: Product2<Range<Int>, Base>.Index
85+
86+
@inlinable
87+
internal init(_ productIndex: Product2<Range<Int>, Base>.Index) {
88+
self.productIndex = productIndex
89+
}
90+
91+
@inlinable
92+
public static func == (lhs: Index, rhs: Index) -> Bool {
93+
lhs.productIndex == rhs.productIndex
94+
}
95+
96+
@inlinable
97+
public static func < (lhs: Index, rhs: Index) -> Bool {
98+
lhs.productIndex < rhs.productIndex
99+
}
100+
}
101+
102+
@inlinable
103+
public var startIndex: Index {
104+
Index(product.startIndex)
105+
}
106+
107+
@inlinable
108+
public var endIndex: Index {
109+
Index(product.endIndex)
110+
}
111+
112+
@inlinable
113+
public subscript(_ index: Index) -> Element {
114+
product[index.productIndex].1
115+
}
116+
117+
@inlinable
118+
public func index(after i: Index) -> Index {
119+
let productIndex = product.index(after: i.productIndex)
120+
return Index(productIndex)
121+
}
122+
123+
@inlinable
124+
public func distance(from start: Index, to end: Index) -> Int {
125+
product.distance(from: start.productIndex, to: end.productIndex)
126+
}
127+
128+
@inlinable
129+
public func index(_ i: Index, offsetBy distance: Int) -> Index {
130+
let productIndex = product.index(i.productIndex, offsetBy: distance)
131+
return Index(productIndex)
132+
}
133+
134+
@inlinable
135+
public func index(
136+
_ i: Index,
137+
offsetBy distance: Int,
138+
limitedBy limit: Index
139+
) -> Index? {
140+
guard let productIndex = product.index(i.productIndex,
141+
offsetBy: distance,
142+
limitedBy: limit.productIndex) else {
143+
return nil
144+
}
145+
return Index(productIndex)
146+
}
147+
148+
@inlinable
149+
public var count: Int {
150+
product.count
151+
}
152+
}
153+
154+
extension FiniteCycle: BidirectionalCollection
155+
where Base: BidirectionalCollection {
156+
@inlinable
157+
public func index(before i: Index) -> Index {
158+
let productIndex = product.index(before: i.productIndex)
159+
return Index(productIndex)
160+
}
161+
}
162+
163+
extension FiniteCycle: RandomAccessCollection
164+
where Base: RandomAccessCollection {}
165+
166+
extension FiniteCycle: Equatable where Base: Equatable {}
167+
extension FiniteCycle: Hashable where Base: Hashable {}
168+
60169
//===----------------------------------------------------------------------===//
61170
// cycled()
62171
//===----------------------------------------------------------------------===//
@@ -110,7 +219,7 @@ extension Collection {
110219
///
111220
/// - Complexity: O(1)
112221
@inlinable
113-
public func cycled(times: Int) -> FlattenSequence<Repeated<Self>> {
114-
repeatElement(self, count: times).joined()
222+
public func cycled(times: Int) -> FiniteCycle<Self> {
223+
FiniteCycle(base: self, times: times)
115224
}
116225
}

Sources/Algorithms/Product.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@ extension Product2: Sequence {
4747
}
4848

4949
@inlinable
50-
public mutating func next() -> (Base1.Element, Base2.Element)? {
50+
public mutating func next() -> (Base1.Element,
51+
Base2.Element)? {
5152
// This is the initial state, where i1.next() has never
5253
// been called, or the final state, where i1.next() has
5354
// already returned nil.
@@ -124,7 +125,8 @@ extension Product2: Collection where Base1: Collection {
124125
}
125126

126127
@inlinable
127-
public subscript(position: Index) -> (Base1.Element, Base2.Element) {
128+
public subscript(position: Index) -> (Base1.Element,
129+
Base2.Element) {
128130
(base1[position.i1], base2[position.i2])
129131
}
130132

Tests/SwiftAlgorithmsTests/CycleTests.swift

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,28 +19,70 @@ final class CycleTests: XCTestCase {
1919
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4],
2020
cycle.prefix(20)
2121
)
22+
}
2223

24+
func testCycleClosedRangePrefix() {
2325
let a = Array((0..<17).cycled().prefix(10_000))
2426
XCTAssertEqual(10_000, a.count)
25-
27+
}
28+
29+
func testEmptyCycle() {
2630
let empty = Array("".cycled())
2731
XCTAssert(empty.isEmpty)
2832
}
29-
33+
34+
func testCycleLazy() {
35+
XCTAssertLazySequence((1...4).lazy.cycled())
36+
}
37+
3038
func testRepeated() {
3139
let repeats = (1...4).cycled(times: 3)
3240
XCTAssertEqualSequences(
3341
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4],
3442
repeats)
35-
43+
}
44+
45+
func testRepeatedClosedRange() {
46+
let repeats = Array((1..<5).cycled(times: 2500))
47+
XCTAssertEqual(10_000, repeats.count)
48+
}
49+
50+
func testRepeatedEmptyCollection() {
3651
let empty1 = Array("".cycled(times: 100))
3752
XCTAssert(empty1.isEmpty)
38-
53+
}
54+
55+
func testRepeatedZeroTimesCycle() {
3956
let empty2 = Array("Hello".cycled(times: 0))
4057
XCTAssert(empty2.isEmpty)
4158
}
42-
43-
func testCycleLazy() {
44-
XCTAssertLazySequence((1...4).lazy.cycled())
59+
60+
func testRepeatedLazy() {
61+
XCTAssertLazySequence((1...4).lazy.cycled(times: 3))
62+
}
63+
64+
func testRepeatedIndexMethods() {
65+
let cycle = (1..<5).cycled(times: 2)
66+
let startIndex = cycle.startIndex
67+
var nextIndex = cycle.index(after: startIndex)
68+
XCTAssertEqual(cycle.distance(from: startIndex, to: nextIndex), 1)
69+
70+
nextIndex = cycle.index(nextIndex, offsetBy: 5)
71+
XCTAssertEqual(cycle.distance(from: startIndex, to: nextIndex), 6)
72+
73+
nextIndex = cycle.index(nextIndex, offsetBy: 2, limitedBy: cycle.endIndex)!
74+
XCTAssertEqual(cycle.distance(from: startIndex, to: nextIndex), 8)
75+
76+
let outOfBounds = cycle.index(nextIndex, offsetBy: 1,
77+
limitedBy: cycle.endIndex)
78+
XCTAssertNil(outOfBounds)
79+
80+
let previousIndex = cycle.index(before: nextIndex)
81+
XCTAssertEqual(cycle.distance(from: startIndex, to: previousIndex), 7)
82+
}
83+
84+
func testRepeatedCount() {
85+
let cycle = (1..<5).cycled(times: 2)
86+
XCTAssertEqual(cycle.count, 8)
4587
}
4688
}

0 commit comments

Comments
 (0)