Skip to content

Commit ff01a02

Browse files
authored
Add Stride for Collection (#24)
2 parents 93c8724 + dfcc611 commit ff01a02

File tree

3 files changed

+427
-0
lines changed

3 files changed

+427
-0
lines changed

Guides/Stride.md

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# Stride
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Stride.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/StrideTests.swift)]
5+
6+
A type that steps over sequence elements by the specified amount.
7+
8+
This is available through the `striding(by:)` method on any `Sequence`.
9+
10+
```swift
11+
(0...10).striding(by: 2) // == [0, 2, 4, 6, 8, 10]
12+
```
13+
14+
If the stride is larger than the count, the resulting wrapper only contains the
15+
first element.
16+
17+
The stride amount must be a positive value.
18+
19+
## Detailed Design
20+
21+
The `striding(by:)` method is declared as a `Sequence` extension, and returns a
22+
`Stride` type:
23+
24+
```swift
25+
extension Sequence {
26+
public func striding(by step: Int) -> Stride<Self>
27+
}
28+
```
29+
30+
A custom `Index` type is defined so that it's not possible to get confused when trying
31+
to access an index of the stride collection.
32+
33+
```swift
34+
[0, 1, 2, 3, 4].striding(by: 2)[1] // == 1
35+
[0, 1, 2, 3, 4].striding(by: 2).map { $0 }[1] // == 2
36+
```
37+
38+
A careful thought was given to the composition of these strides by giving a custom
39+
implementation to `index(_:offsetBy:limitedBy)` which multiplies the offset by the
40+
stride amount.
41+
42+
```swift
43+
base.index(i.base, offsetBy: distance * stride, limitedBy: base.endIndex)
44+
```
45+
46+
The following two lines of code are equivalent, including performance:
47+
48+
```swift
49+
(0...10).striding(by: 6)
50+
(0...10).striding(by: 2).stride(by: 3)
51+
```
52+
53+
### Complexity
54+
55+
The call to `striding(by: k)` is always O(_1_) and access to the next value in the stride
56+
is O(_1_) if the collection conforms to `RandomAccessCollection`, otherwise O(_k_).
57+
58+
### Comparison with other languages
59+
60+
[rust has `Strided`](https://docs.rs/strided/0.2.9/strided/) available in a crate.
61+
[c++ has std::slice::stride](http://www.cplusplus.com/reference/valarray/slice/stride/)
62+
63+
The semantics of `striding` described in this documentation are equivalent.

Sources/Algorithms/Stride.swift

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
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+
//===----------------------------------------------------------------------===//
13+
// striding(by:)
14+
//===----------------------------------------------------------------------===//
15+
16+
extension Sequence {
17+
/// Returns a sequence stepping through the elements every `step` starting
18+
/// at the first value. Any remainders of the stride will be trimmed.
19+
///
20+
/// (0...10).striding(by: 2) // == [0, 2, 4, 6, 8, 10]
21+
/// (0...10).striding(by: 3) // == [0, 3, 6, 9]
22+
///
23+
/// - Complexity: O(1). Access to successive values is O(1) if the
24+
/// collection conforms to `RandomAccessCollection`; otherwise,
25+
/// O(_k_), where _k_ is the striding `step`.
26+
///
27+
/// - Parameter step: The amount to step with each iteration.
28+
/// - Returns: Returns a sequence or collection for stepping through the
29+
/// elements by the specified amount.
30+
public func striding(by step: Int) -> Stride<Self> {
31+
Stride(base: self, stride: step)
32+
}
33+
}
34+
35+
public struct Stride<Base: Sequence> {
36+
37+
let base: Base
38+
let stride: Int
39+
40+
init(base: Base, stride: Int) {
41+
precondition(stride > 0, "striding must be greater than zero")
42+
self.base = base
43+
self.stride = stride
44+
}
45+
}
46+
47+
extension Stride {
48+
public func striding(by step: Int) -> Self {
49+
Stride(base: base, stride: stride * step)
50+
}
51+
}
52+
53+
extension Stride: Sequence {
54+
55+
public struct Iterator: IteratorProtocol {
56+
57+
var iterator: Base.Iterator
58+
let stride: Int
59+
var striding: Bool = false
60+
61+
public mutating func next() -> Base.Element? {
62+
guard striding else {
63+
striding = true
64+
return iterator.next()
65+
}
66+
for _ in 0..<stride - 1 {
67+
guard iterator.next() != nil else { break }
68+
}
69+
return iterator.next()
70+
}
71+
}
72+
73+
public func makeIterator() -> Stride<Base>.Iterator {
74+
Iterator(iterator: base.makeIterator(), stride: stride)
75+
}
76+
}
77+
78+
extension Stride: Collection where Base: Collection {
79+
80+
public struct Index: Comparable {
81+
82+
let base: Base.Index
83+
84+
init(_ base: Base.Index) {
85+
self.base = base
86+
}
87+
88+
public static func < (lhs: Index, rhs: Index) -> Bool {
89+
lhs.base < rhs.base
90+
}
91+
}
92+
93+
public var startIndex: Index {
94+
Index(base.startIndex)
95+
}
96+
97+
public var endIndex: Index {
98+
Index(base.endIndex)
99+
}
100+
101+
public subscript(i: Index) -> Base.Element {
102+
base[i.base]
103+
}
104+
105+
public func index(after i: Index) -> Index {
106+
precondition(i.base < base.endIndex, "Advancing past end index")
107+
return index(i, offsetBy: 1)
108+
}
109+
110+
public func index(
111+
_ i: Index,
112+
offsetBy n: Int,
113+
limitedBy limit: Index
114+
) -> Index? {
115+
guard n != 0 else { return i }
116+
guard limit != i else { return nil }
117+
118+
return n > 0
119+
? offsetForward(i, offsetBy: n, limitedBy: limit)
120+
: offsetBackward(i, offsetBy: -n, limitedBy: limit)
121+
}
122+
123+
private func offsetForward(
124+
_ i: Index,
125+
offsetBy n: Int,
126+
limitedBy limit: Index
127+
) -> Index? {
128+
if limit < i {
129+
if let idx = base.index(
130+
i.base,
131+
offsetBy: n * stride,
132+
limitedBy: base.endIndex
133+
) {
134+
return Index(idx)
135+
} else {
136+
assert(distance(from: i, to: endIndex) == n, "Advancing past end index")
137+
return endIndex
138+
}
139+
} else if let idx = base.index(
140+
i.base,
141+
offsetBy: n * stride,
142+
limitedBy: limit.base
143+
) {
144+
return Index(idx)
145+
} else {
146+
return distance(from: i, to: limit) == n
147+
? endIndex
148+
: nil
149+
}
150+
}
151+
152+
private func offsetBackward(
153+
_ i: Index,
154+
offsetBy n: Int,
155+
limitedBy limit: Index
156+
) -> Index? {
157+
let distance = i == endIndex
158+
? -((base.count - 1) % stride + 1) + (n - 1) * -stride
159+
: n * -stride
160+
return base.index(
161+
i.base,
162+
offsetBy: distance,
163+
limitedBy: limit.base
164+
).map(Index.init)
165+
}
166+
167+
public var count: Int {
168+
base.isEmpty ? 0 : (base.count - 1) / stride + 1
169+
}
170+
171+
public func distance(from start: Index, to end: Index) -> Int {
172+
let distance = base.distance(from: start.base, to: end.base)
173+
return distance / stride + (distance % stride).signum()
174+
}
175+
176+
public func index(_ i: Index, offsetBy distance: Int) -> Index {
177+
precondition(distance <= 0 || i.base < base.endIndex, "Advancing past end index")
178+
precondition(distance >= 0 || i.base > base.startIndex, "Incrementing past start index")
179+
let limit = distance > 0 ? endIndex : startIndex
180+
let idx = index(i, offsetBy: distance, limitedBy: limit)
181+
precondition(idx != nil, "The distance \(distance) is not valid for this collection")
182+
return idx!
183+
}
184+
}
185+
186+
extension Stride: BidirectionalCollection
187+
where Base: RandomAccessCollection {
188+
189+
public func index(before i: Index) -> Index {
190+
precondition(i.base > base.startIndex, "Incrementing past start index")
191+
return index(i, offsetBy: -1)
192+
}
193+
}
194+
195+
extension Stride: RandomAccessCollection
196+
where Base: RandomAccessCollection {}
197+
198+
extension Stride: Equatable
199+
where Base.Element: Equatable {
200+
201+
public static func == (lhs: Stride, rhs: Stride) -> Bool {
202+
lhs.elementsEqual(rhs, by: ==)
203+
}
204+
205+
}
206+
207+
extension Stride: Hashable
208+
where Base.Element: Hashable {
209+
210+
public func hash(into hasher: inout Hasher) {
211+
hasher.combine(stride)
212+
for element in self {
213+
hasher.combine(element)
214+
}
215+
}
216+
217+
}
218+
219+
extension Stride.Index: Hashable
220+
where Base.Index: Hashable {}

0 commit comments

Comments
 (0)