Skip to content

Commit 63311ae

Browse files
authored
Merge pull request #79 from apple/nate/just_windows
Rename slidingWindows back to windows(ofCount:)
2 parents 5be792a + cc20b55 commit 63311ae

File tree

5 files changed

+119
-114
lines changed

5 files changed

+119
-114
lines changed

Guides/SlidingWindows.md

Lines changed: 0 additions & 64 deletions
This file was deleted.

Guides/Windows.md

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Windows
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Windows.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/WindowsTests.swift)]
5+
6+
Break a collection into overlapping subsequences where
7+
elements are slices from the original collection.
8+
9+
The `windows(ofCount:)` method takes a size as a parameter and returns a collection of subsequences. Each element of the returned collection is a successive overlapping slice of the given size.
10+
11+
```swift
12+
let swift = "swift"
13+
14+
let windowed = swift.windows(ofCount: 2)
15+
// Array(windowed) == [ "sw", "wi", "if", "ft" ]
16+
```
17+
18+
## Detailed Design
19+
20+
The `windows(ofCount:)` method is added as an extension `Collection` method:
21+
22+
```swift
23+
extension Collection {
24+
public func windows(ofCount count: Int) -> Windows<Self>
25+
}
26+
```
27+
28+
If a size larger than the collection's length is specified, the returned collection is empty.
29+
30+
```swift
31+
[1, 2, 3].windows(ofCount: 5).isEmpty // true
32+
```
33+
34+
The resulting `Windows` type is a collection, with conditional conformance to the
35+
`BidirectionalCollection`, `RandomAccessCollection`, and
36+
`LazyCollectionProtocol` protocols when the base collection conforms.
37+
38+
### Complexity
39+
40+
The call to `windows(ofCount: k)` is O(1) if the collection conforms to
41+
`RandomAccessCollection`, otherwise O(_k_). Access to each successive window is O(1).
42+
43+
### Naming
44+
45+
The method and type name take their names from the sliding windows algorithm.
46+
47+
The `ofCount` parameter label was chosen to create a consistent feel with other
48+
APIs in the `Algorithms` package, specifically with `combinations(ofCount:)`
49+
and `permutations(ofCount:)`.
50+
51+
### Comparison with other languages
52+
53+
[rust](https://doc.rust-lang.org/std/slice/struct.Windows.html) has
54+
`std::slice::Windows` which is a method available on slices. It has the same
55+
semantics as described here.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
3737
- [`chunked(by:)`, `chunked(on:)`, `chunks(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Chunked.md): Eager and lazy operations that break a collection into chunks based on either a binary predicate or when the result of a projection changes or chunks of a given count.
3838
- [`indexed()`](https://github.com/apple/swift-algorithms/blob/main/Guides/Indexed.md): Iterate over tuples of a collection's indices and elements.
3939
- [`trimming(where:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Trim.md): Returns a slice by trimming elements from a collection's start and end.
40-
- [`slidingWindows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SlidingWindows.md): Breaks a collection into overlapping contiguous window subsequences where elements are slices from the original collection.
40+
- [`windows(ofCount:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/SlidingWindows.md): Breaks a collection into overlapping subsequences where elements are slices from the original collection.
4141
- [`striding(by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/Stride.md): Returns every nth element of a collection.
4242

4343
## Adding Swift Algorithms as a Dependency

Sources/Algorithms/SlidingWindows.swift renamed to Sources/Algorithms/Windows.swift

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,58 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
//===----------------------------------------------------------------------===//
13-
// slidingWindows(ofCount:)
13+
// windows(ofCount:)
1414
//===----------------------------------------------------------------------===//
1515

1616
extension Collection {
17-
/// A collection for all contiguous windows of length size, the
18-
/// windows overlap.
17+
/// Returns a collection of all the overlapping slices of a given size.
1918
///
20-
/// - Complexity: O(*1*) if the collection conforms to
21-
/// `RandomAccessCollection`, otherwise O(*k*) where `k` is `count`.
22-
/// Access to the next window is O(*1*).
19+
/// Use this method to iterate over overlapping subsequences of this
20+
/// collection. This example prints every five character substring of `str`:
21+
///
22+
/// let str = "Hello, world!"
23+
/// for substring in str.windows(ofCount: 5) {
24+
/// print(substring)
25+
/// }
26+
/// // "Hello"
27+
/// // "ello,"
28+
/// // "llo, "
29+
/// // "lo, W"
30+
/// // ...
31+
/// // "orld!"
2332
///
2433
/// - Parameter count: The number of elements in each window subsequence.
34+
/// - Returns: A collection of subsequences of this collection, each with
35+
/// length `count`. If this collection is shorter than `count`, the
36+
/// resulting collection is empty.
2537
///
26-
/// - Returns: If the collection is shorter than `size` the resulting
27-
/// SlidingWindows collection will be empty.
28-
public func slidingWindows(ofCount count: Int) -> SlidingWindows<Self> {
29-
SlidingWindows(base: self, size: count)
38+
/// - Complexity: O(1) if the collection conforms to
39+
/// `RandomAccessCollection`, otherwise O(*k*) where `k` is `count`.
40+
/// Access to successive windows is O(1).
41+
public func windows(ofCount count: Int) -> Windows<Self> {
42+
Windows(base: self, size: count)
3043
}
3144
}
3245

3346
/// A collection wrapper that presents a sliding window over the elements of
3447
/// a collection.
35-
public struct SlidingWindows<Base: Collection> {
48+
public struct Windows<Base: Collection> {
3649
public let base: Base
3750
public let size: Int
3851

3952
internal var firstUpperBound: Base.Index?
4053

4154
internal init(base: Base, size: Int) {
42-
precondition(size > 0, "SlidingWindows size must be greater than zero")
55+
precondition(size > 0, "Windows size must be greater than zero")
4356
self.base = base
4457
self.size = size
45-
self.firstUpperBound = base.index(base.startIndex, offsetBy: size, limitedBy: base.endIndex)
58+
self.firstUpperBound =
59+
base.index(base.startIndex, offsetBy: size, limitedBy: base.endIndex)
4660
}
4761
}
4862

49-
extension SlidingWindows: Collection {
50-
/// A position in a `SlidingWindows` collection.
63+
extension Windows: Collection {
64+
/// A position in a `Windows` collection.
5165
public struct Index: Comparable {
5266
internal var lowerBound: Base.Index
5367
internal var upperBound: Base.Index
@@ -74,7 +88,9 @@ extension SlidingWindows: Collection {
7488
}
7589

7690
public subscript(index: Index) -> Base.SubSequence {
77-
precondition(index.lowerBound != index.upperBound, "SlidingWindows index is out of range")
91+
precondition(
92+
index.lowerBound != index.upperBound,
93+
"Windows index is out of range")
7894
return base[index.lowerBound..<index.upperBound]
7995
}
8096

@@ -285,7 +301,7 @@ extension SlidingWindows: Collection {
285301
}
286302
}
287303

288-
extension SlidingWindows: BidirectionalCollection where Base: BidirectionalCollection {
304+
extension Windows: BidirectionalCollection where Base: BidirectionalCollection {
289305
public func index(before index: Index) -> Index {
290306
precondition(index > startIndex, "Incrementing past start index")
291307
if index == endIndex {
@@ -302,9 +318,9 @@ extension SlidingWindows: BidirectionalCollection where Base: BidirectionalColle
302318
}
303319
}
304320

305-
extension SlidingWindows: LazySequenceProtocol where Base: LazySequenceProtocol {}
306-
extension SlidingWindows: LazyCollectionProtocol where Base: LazyCollectionProtocol {}
307-
extension SlidingWindows: RandomAccessCollection where Base: RandomAccessCollection {}
308-
extension SlidingWindows: Equatable where Base: Equatable {}
309-
extension SlidingWindows: Hashable where Base: Hashable, Base.Index: Hashable {}
310-
extension SlidingWindows.Index: Hashable where Base.Index: Hashable {}
321+
extension Windows: LazySequenceProtocol where Base: LazySequenceProtocol {}
322+
extension Windows: LazyCollectionProtocol where Base: LazyCollectionProtocol {}
323+
extension Windows: RandomAccessCollection where Base: RandomAccessCollection {}
324+
extension Windows: Equatable where Base: Equatable {}
325+
extension Windows: Hashable where Base: Hashable, Base.Index: Hashable {}
326+
extension Windows.Index: Hashable where Base.Index: Hashable {}

Tests/SwiftAlgorithmsTests/SlidingWindowsTests.swift renamed to Tests/SwiftAlgorithmsTests/WindowsTests.swift

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@
1212
import XCTest
1313
@testable import Algorithms
1414

15-
final class SlidingWindowsTests: XCTestCase {
15+
final class windowsTests: XCTestCase {
1616

1717
func testWindowsOfString() {
18-
1918
let s = "swift"
20-
let w = s.slidingWindows(ofCount: 2)
19+
let w = s.windows(ofCount: 2)
2120
var i = w.startIndex
2221

2322
XCTAssertEqualSequences(w[i], "sw")
@@ -28,30 +27,29 @@ final class SlidingWindowsTests: XCTestCase {
2827
w.formIndex(after: &i)
2928
XCTAssertEqualSequences(w[i], "ft")
3029

31-
// w.index(after: w.endIndex) // ← Precondition failed: SlidingWindows index is out of range
32-
// w.index(before: w.startIndex) // ← Precondition failed: SlidingWindows index is out of range
33-
// w.formIndex(after: &i); w[i] // ← Precondition failed: SlidingWindows index is out of range
30+
// w.index(after: w.endIndex) // ← Precondition failed: windows index is out of range
31+
// w.index(before: w.startIndex) // ← Precondition failed: windows index is out of range
32+
// w.formIndex(after: &i); w[i] // ← Precondition failed: windows index is out of range
3433
}
3534

3635
func testWindowsOfRange() {
3736
let a = 0...100
3837

39-
XCTAssertTrue(a.slidingWindows(ofCount: 200).isEmpty)
38+
XCTAssertTrue(a.windows(ofCount: 200).isEmpty)
4039

41-
let w = a.slidingWindows(ofCount: 10)
40+
let w = a.windows(ofCount: 10)
4241

4342
XCTAssertEqualSequences(w.first!, 0..<10)
4443
XCTAssertEqualSequences(w.last!, 91..<101)
4544
}
4645

4746
func testWindowsOfInt() {
48-
49-
let a = [ 0, 1, 0, 1 ].slidingWindows(ofCount: 2)
47+
let a = [ 0, 1, 0, 1 ].windows(ofCount: 2)
5048

5149
XCTAssertEqual(a.count, 3)
5250
XCTAssertEqual(a.map { $0.reduce(0, +) }, [1, 1, 1])
5351

54-
let a2 = [0, 1, 2, 3, 4, 5, 6].slidingWindows(ofCount: 3).map {
52+
let a2 = [0, 1, 2, 3, 4, 5, 6].windows(ofCount: 3).map {
5553
$0.reduce(0, +)
5654
}.reduce(0, +)
5755

@@ -60,27 +58,27 @@ final class SlidingWindowsTests: XCTestCase {
6058

6159
func testWindowsCount() {
6260
let a = [0, 1, 2, 3, 4, 5]
63-
XCTAssertEqual(a.slidingWindows(ofCount: 3).count, 4)
61+
XCTAssertEqual(a.windows(ofCount: 3).count, 4)
6462

6563
let a2 = [0, 1, 2, 3, 4]
66-
XCTAssertEqual(a2.slidingWindows(ofCount: 6).count, 0)
64+
XCTAssertEqual(a2.windows(ofCount: 6).count, 0)
6765

6866
let a3 = [Int]()
69-
XCTAssertEqual(a3.slidingWindows(ofCount: 2).count, 0)
67+
XCTAssertEqual(a3.windows(ofCount: 2).count, 0)
7068
}
7169

7270
func testWindowsSecondAndLast() {
7371
let a = [0, 1, 2, 3, 4, 5]
74-
let w = a.slidingWindows(ofCount: 4)
72+
let w = a.windows(ofCount: 4)
7573
let snd = w[w.index(after: w.startIndex)]
7674
XCTAssertEqualSequences(snd, [1, 2, 3, 4])
7775

78-
let w2 = a.slidingWindows(ofCount: 3)
76+
let w2 = a.windows(ofCount: 3)
7977
XCTAssertEqualSequences(w2.last!, [3, 4, 5])
8078
}
8179

8280
func testWindowsIndexAfterAndBefore() {
83-
let a = [0, 1, 2, 3, 4, 5].slidingWindows(ofCount: 2)
81+
let a = [0, 1, 2, 3, 4, 5].windows(ofCount: 2)
8482
var i = a.startIndex
8583
a.formIndex(after: &i)
8684
a.formIndex(after: &i)
@@ -90,14 +88,14 @@ final class SlidingWindowsTests: XCTestCase {
9088

9189
func testWindowsIndexTraversals() {
9290
validateIndexTraversals(
93-
"".slidingWindows(ofCount: 1),
94-
"a".slidingWindows(ofCount: 1),
95-
"ab".slidingWindows(ofCount: 1),
96-
"abc".slidingWindows(ofCount: 1),
97-
"".slidingWindows(ofCount: 3),
98-
"a".slidingWindows(ofCount: 3),
99-
"abc".slidingWindows(ofCount: 3),
100-
"abcdefgh".slidingWindows(ofCount: 3),
91+
"".windows(ofCount: 1),
92+
"a".windows(ofCount: 1),
93+
"ab".windows(ofCount: 1),
94+
"abc".windows(ofCount: 1),
95+
"".windows(ofCount: 3),
96+
"a".windows(ofCount: 3),
97+
"abc".windows(ofCount: 3),
98+
"abcdefgh".windows(ofCount: 3),
10199
indices: { windows in
102100
let endIndex = windows.base.endIndex
103101
let indices = windows.base.indices + [endIndex]
@@ -108,6 +106,6 @@ final class SlidingWindowsTests: XCTestCase {
108106
}
109107

110108
func testWindowsLazy() {
111-
XCTAssertLazyCollection([0, 1, 2, 3].lazy.slidingWindows(ofCount: 2))
109+
XCTAssertLazyCollection([0, 1, 2, 3].lazy.windows(ofCount: 2))
112110
}
113111
}

0 commit comments

Comments
 (0)