Skip to content

Commit 18bd60d

Browse files
committed
Add Stride for Collection
1 parent dd2ad89 commit 18bd60d

File tree

3 files changed

+247
-0
lines changed

3 files changed

+247
-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 a collection’s elements by the specified amount.
7+
8+
This is available through the `stride(by:)` method on any `Collection`.
9+
10+
```swift
11+
(0...10).stride(by: 2) // == [0, 2, 4, 6, 8, 10]
12+
```
13+
14+
If the stride is larger than the collection 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 `stride(by:)` method is declared as a `Collection` extension, and returns a
22+
`Stride` type:
23+
24+
```swift
25+
extension Collection {
26+
public func stride(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].stride(by: 2)[1] // == 1
35+
[0, 1, 2, 3, 4].stride(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).stride(by: 6)
50+
(0...10).stride(by: 2).stride(by: 3)
51+
```
52+
53+
### Complexity
54+
55+
The call to `stride(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 `stride` described in this documentation are equivalent.

Sources/Algorithms/Stride.swift

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
// stride(by:)
14+
//===----------------------------------------------------------------------===//
15+
16+
extension Collection {
17+
/// - Complexity: O(_1_) and access to the next value in the stride
18+
/// is O(_1_) if the collection conforms to `RandomAccessCollection`,
19+
/// otherwise O(_k_) where `k` is the stride `step`.
20+
///
21+
/// - Parameter step: The amount to step with each iteration.
22+
/// - Returns: Returns a collection stepping through the elements by the
23+
/// specified amount.
24+
public func stride(by step: Int) -> Stride<Self> {
25+
Stride(base: self, stride: step)
26+
}
27+
}
28+
29+
public struct Stride<Base: Collection> {
30+
31+
public let base: Base
32+
public let stride: Int
33+
34+
init(base: Base, stride: Int) {
35+
precondition(stride > 0, "stride must be greater than zero")
36+
self.base = base
37+
self.stride = stride
38+
}
39+
}
40+
41+
extension Stride: Collection {
42+
43+
public struct Index: Comparable {
44+
45+
let base: Base.Index
46+
47+
init(_ base: Base.Index) {
48+
self.base = base
49+
}
50+
51+
public static func < (lhs: Index, rhs: Index) -> Bool {
52+
lhs.base < rhs.base
53+
}
54+
}
55+
56+
public var startIndex: Index {
57+
Index(base.startIndex)
58+
}
59+
60+
public var endIndex: Index {
61+
Index(base.endIndex)
62+
}
63+
64+
public subscript(i: Index) -> Base.Element {
65+
base[i.base]
66+
}
67+
68+
public func index(after i: Index) -> Index {
69+
precondition(i.base < base.endIndex, "Advancing past end index")
70+
return base.index(i.base, offsetBy: stride, limitedBy: base.endIndex)
71+
.map(Index.init) ?? endIndex
72+
}
73+
74+
public func index(
75+
_ i: Index,
76+
offsetBy distance: Int,
77+
limitedBy limit: Index
78+
) -> Index? {
79+
base.index(i.base, offsetBy: distance * stride, limitedBy: limit.base)
80+
.map(Index.init)
81+
}
82+
83+
// TODO: Implement distance(from:to:) and index(_:offsetBy:)
84+
85+
}
86+
87+
extension Stride: BidirectionalCollection
88+
where Base: RandomAccessCollection {
89+
90+
public func index(before i: Index) -> Index {
91+
precondition(i.base > base.startIndex, "Incrementing past start index")
92+
if i == endIndex {
93+
let count = base.count
94+
precondition(count > 0, "Asking for an index when count is zero")
95+
return Index(
96+
base.index(
97+
base.endIndex,
98+
offsetBy: -((count - 1) % stride + 1)
99+
)
100+
)
101+
} else {
102+
return base.index(i.base, offsetBy: -stride, limitedBy: startIndex.base)
103+
.map(Index.init) ?? startIndex
104+
}
105+
}
106+
}
107+
108+
extension Stride: RandomAccessCollection
109+
where Base: RandomAccessCollection {}
110+
111+
extension Stride: Equatable
112+
where Base: Equatable {}
113+
114+
extension Stride: Hashable
115+
where Base: Hashable, Base.Index: Hashable {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
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 XCTest
13+
import Algorithms
14+
15+
final class StridingTests: XCTestCase {
16+
17+
func testStride() {
18+
let a = (0...10)
19+
XCTAssertEqualSequences(a.stride(by: 1), (0...10))
20+
XCTAssertEqualSequences(a.stride(by: 2), [0, 2, 4, 6, 8, 10])
21+
XCTAssertEqualSequences(a.stride(by: 3), [0, 3, 6, 9])
22+
XCTAssertEqualSequences(a.stride(by: 4), [0, 4, 8])
23+
XCTAssertEqualSequences(a.stride(by: 5), [0, 5, 10])
24+
XCTAssertEqualSequences(a.stride(by: 10), [0, 10])
25+
XCTAssertEqualSequences(a.stride(by: 11), [0])
26+
}
27+
28+
func testStrideString() {
29+
let s = "swift"
30+
XCTAssertEqualSequences(s.stride(by: 2), ["s", "i", "t"])
31+
}
32+
33+
func testStrideReversed() {
34+
let a = [0, 1, 2, 3, 4, 5]
35+
XCTAssertEqualSequences(a.stride(by: 3).reversed(), [3, 0])
36+
XCTAssertEqualSequences(a.reversed().stride(by: 2), [5, 3, 1])
37+
}
38+
39+
func testStrideIndexes() {
40+
let a = [0, 1, 2, 3, 4, 5].stride(by: 2)
41+
var i = a.startIndex
42+
XCTAssertEqual(a[i], 0)
43+
a.formIndex(after: &i)
44+
XCTAssertEqual(a[i], 2)
45+
a.formIndex(after: &i)
46+
XCTAssertEqual(a[i], 4)
47+
a.formIndex(before: &i)
48+
XCTAssertEqual(a[i], 2)
49+
a.formIndex(before: &i)
50+
XCTAssertEqual(a[i], 0)
51+
// a.formIndex(before: &i) // Precondition failed: Advancing past start index
52+
// a.index(after: a.endIndex) // Precondition failed: Advancing past end index
53+
}
54+
55+
func testStrideCompositionEquivalence() {
56+
let a = (0...10)
57+
XCTAssertEqualSequences(a.stride(by: 6), a.stride(by: 2).stride(by: 3))
58+
}
59+
60+
func testStrideLast() {
61+
XCTAssertEqual((1...10).stride(by: 2).last, 9) // 1, 3, 5, 7, 9
62+
XCTAssertEqual((1...10).stride(by: 3).last, 10) // 1, 4, 7, 10
63+
XCTAssertEqual((1...10).stride(by: 4).last, 9) // 1, 5, 9
64+
XCTAssertEqual((1...10).stride(by: 5).last, 6) // 1, 6
65+
XCTAssertEqual((1...100).stride(by: 50).last, 51) // 1, 51
66+
XCTAssertEqual((1...5).stride(by: 2).last, 5) // 1, 3, 5
67+
XCTAssertEqual([Int]().stride(by: 2).last, nil) // 1, 3, 5
68+
}
69+
}

0 commit comments

Comments
 (0)