Skip to content

Commit 0f27d99

Browse files
authored
Introduce adjacentPairs (#119)
1 parent 66a56e3 commit 0f27d99

File tree

5 files changed

+423
-1
lines changed

5 files changed

+423
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ package updates, you can specify your package dependency using
1212

1313
## [Unreleased]
1414

15-
*No changes yet.*
15+
-`adjacentPairs()` lazily iterates over tuples of adjacent elements of a sequence.
1616

1717
---
1818

Guides/AdjacentPairs.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# AdjacentPairs
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/AdjacentPairs.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/AdjacentPairsTests.swift)]
5+
6+
Lazily iterates over tuples of adjacent elements.
7+
8+
This operation is available for any sequence by calling the `adjacentPairs()` method.
9+
10+
```swift
11+
let numbers = (1...5)
12+
let pairs = numbers.adjacentPairs()
13+
// Array(pairs) == [(1, 2), (2, 3), (3, 4), (4, 5)]
14+
```
15+
16+
## Detailed Design
17+
18+
The `adjacentPairs()` method is declared as a `Sequence` extension returning `AdjacentPairsSequence` and as a `Collection` extension returning `AdjacentPairsCollection`.
19+
20+
```swift
21+
extension Sequence {
22+
public func adjacentPairs() -> AdjacentPairsSequence<Self>
23+
}
24+
```
25+
26+
```swift
27+
extension Collection {
28+
public func adjacentPairs() -> AdjacentPairsCollection<Self>
29+
}
30+
```
31+
32+
The `AdjacentPairsSequence` type is a sequence, and the `AdjacentPairsCollection` type is a collection with conditional conformance to `BidirectionalCollection` and `RandomAccessCollection` when the underlying collection conforms.
33+
34+
### Complexity
35+
36+
Calling `adjacentPairs` is an O(1) operation.
37+
38+
### Naming
39+
40+
This method is named for clarity while remaining agnostic to any particular domain of programming. In natural language processing, this operation is akin to computing a list of bigrams; however, this algorithm is not specific to this use case.
41+
42+
[naming]: https://forums.swift.org/t/naming-of-chained-with/40999/
43+
44+
### Comparison with other languages
45+
46+
This function is often written as a `zip` of a sequence together with itself, minus its first element.
47+
48+
**Haskell:** This operation is spelled ``s `zip` tail s``.
49+
50+
**Python:** Python users may write `zip(s, s[1:])` for a list with at least one element. For natural language processing, the `nltk` package offers a `bigrams` function akin to this method.
51+
52+
Note that in Swift, the spelling `zip(s, s.dropFirst())` is undefined behavior for a single-pass sequence `s`.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
3939

4040
#### Other useful operations
4141

42+
- [`adjacentPairs()`](https://github.com/apple/swift-algorithms/blob/main/Guides/AdjacentPairs.md): Lazily iterates over tuples of adjacent elements.
4243
- [`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.
4344
- [`firstNonNil(_:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/FirstNonNil.md): Returns the first non-`nil` result from transforming a sequence's elements.
4445
- [`indexed()`](https://github.com/apple/swift-algorithms/blob/main/Guides/Indexed.md): Iterate over tuples of a collection's indices and elements.
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
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+
extension Sequence {
13+
/// Creates a sequence of adjacent pairs of elements from this sequence.
14+
///
15+
/// In the `AdjacentPairsSequence` returned by this method, the elements of
16+
/// the *i*th pair are the *i*th and *(i+1)*th elements of the underlying
17+
/// sequence.
18+
/// The following example uses the `adjacentPairs()` method to iterate over
19+
/// adjacent pairs of integers:
20+
///
21+
/// for pair in (1...5).adjacentPairs() {
22+
/// print(pair)
23+
/// }
24+
/// // Prints "(1, 2)"
25+
/// // Prints "(2, 3)"
26+
/// // Prints "(3, 4)"
27+
/// // Prints "(4, 5)"
28+
@inlinable
29+
public func adjacentPairs() -> AdjacentPairsSequence<Self> {
30+
AdjacentPairsSequence(base: self)
31+
}
32+
}
33+
34+
extension Collection {
35+
/// A collection of adjacent pairs of elements built from an underlying collection.
36+
///
37+
/// In an `AdjacentPairsCollection`, the elements of the *i*th pair are the *i*th
38+
/// and *(i+1)*th elements of the underlying sequence. The following example
39+
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
40+
/// integers:
41+
/// ```
42+
/// for pair in (1...5).adjacentPairs() {
43+
/// print(pair)
44+
/// }
45+
/// // Prints "(1, 2)"
46+
/// // Prints "(2, 3)"
47+
/// // Prints "(3, 4)"
48+
/// // Prints "(4, 5)"
49+
/// ```
50+
@inlinable
51+
public func adjacentPairs() -> AdjacentPairsCollection<Self> {
52+
AdjacentPairsCollection(base: self)
53+
}
54+
}
55+
56+
/// A sequence of adjacent pairs of elements built from an underlying sequence.
57+
///
58+
/// In an `AdjacentPairsSequence`, the elements of the *i*th pair are the *i*th
59+
/// and *(i+1)*th elements of the underlying sequence. The following example
60+
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
61+
/// integers:
62+
/// ```
63+
/// for pair in (1...5).adjacentPairs() {
64+
/// print(pair)
65+
/// }
66+
/// // Prints "(1, 2)"
67+
/// // Prints "(2, 3)"
68+
/// // Prints "(3, 4)"
69+
/// // Prints "(4, 5)"
70+
/// ```
71+
public struct AdjacentPairsSequence<Base: Sequence> {
72+
@usableFromInline
73+
internal let base: Base
74+
75+
/// Creates an instance that makes pairs of adjacent elements from `base`.
76+
@inlinable
77+
internal init(base: Base) {
78+
self.base = base
79+
}
80+
}
81+
82+
extension AdjacentPairsSequence {
83+
public struct Iterator {
84+
@usableFromInline
85+
internal var base: Base.Iterator
86+
87+
@usableFromInline
88+
internal var previousElement: Base.Element?
89+
90+
@inlinable
91+
internal init(base: Base.Iterator) {
92+
self.base = base
93+
}
94+
}
95+
}
96+
97+
extension AdjacentPairsSequence.Iterator: IteratorProtocol {
98+
public typealias Element = (Base.Element, Base.Element)
99+
100+
@inlinable
101+
public mutating func next() -> Element? {
102+
if previousElement == nil {
103+
previousElement = base.next()
104+
}
105+
106+
guard let previous = previousElement, let next = base.next() else {
107+
return nil
108+
}
109+
110+
previousElement = next
111+
return (previous, next)
112+
}
113+
}
114+
115+
extension AdjacentPairsSequence: Sequence {
116+
@inlinable
117+
public func makeIterator() -> Iterator {
118+
Iterator(base: base.makeIterator())
119+
}
120+
121+
@inlinable
122+
public var underestimatedCount: Int {
123+
Swift.max(0, base.underestimatedCount - 1)
124+
}
125+
}
126+
127+
/// A collection of adjacent pairs of elements built from an underlying collection.
128+
///
129+
/// In an `AdjacentPairsCollection`, the elements of the *i*th pair are the *i*th
130+
/// and *(i+1)*th elements of the underlying sequence. The following example
131+
/// uses the `adjacentPairs()` method to iterate over adjacent pairs of
132+
/// integers:
133+
/// ```
134+
/// for pair in (1...5).adjacentPairs() {
135+
/// print(pair)
136+
/// }
137+
/// // Prints "(1, 2)"
138+
/// // Prints "(2, 3)"
139+
/// // Prints "(3, 4)"
140+
/// // Prints "(4, 5)"
141+
/// ```
142+
public struct AdjacentPairsCollection<Base: Collection> {
143+
@usableFromInline
144+
internal let base: Base
145+
146+
public let startIndex: Index
147+
148+
@inlinable
149+
internal init(base: Base) {
150+
self.base = base
151+
152+
// Precompute `startIndex` to ensure O(1) behavior,
153+
// avoiding indexing past `endIndex`
154+
let start = base.startIndex
155+
let end = base.endIndex
156+
let second = start == end ? start : base.index(after: start)
157+
self.startIndex = Index(first: start, second: second)
158+
}
159+
}
160+
161+
extension AdjacentPairsCollection {
162+
public typealias Iterator = AdjacentPairsSequence<Base>.Iterator
163+
164+
@inlinable
165+
public func makeIterator() -> Iterator {
166+
Iterator(base: base.makeIterator())
167+
}
168+
}
169+
170+
extension AdjacentPairsCollection {
171+
public struct Index: Comparable {
172+
@usableFromInline
173+
internal var first: Base.Index
174+
175+
@usableFromInline
176+
internal var second: Base.Index
177+
178+
@inlinable
179+
internal init(first: Base.Index, second: Base.Index) {
180+
self.first = first
181+
self.second = second
182+
}
183+
184+
@inlinable
185+
public static func < (lhs: Index, rhs: Index) -> Bool {
186+
(lhs.first, lhs.second) < (rhs.first, rhs.second)
187+
}
188+
}
189+
}
190+
191+
extension AdjacentPairsCollection: Collection {
192+
@inlinable
193+
public var endIndex: Index {
194+
switch base.endIndex {
195+
case startIndex.first, startIndex.second:
196+
return startIndex
197+
case let end:
198+
return Index(first: end, second: end)
199+
}
200+
}
201+
202+
@inlinable
203+
public subscript(position: Index) -> (Base.Element, Base.Element) {
204+
(base[position.first], base[position.second])
205+
}
206+
207+
@inlinable
208+
public func index(after i: Index) -> Index {
209+
let next = base.index(after: i.second)
210+
return next == base.endIndex
211+
? endIndex
212+
: Index(first: i.second, second: next)
213+
}
214+
215+
@inlinable
216+
public func index(_ i: Index, offsetBy distance: Int) -> Index {
217+
if distance == 0 {
218+
return i
219+
} else if distance > 0 {
220+
let firstOffsetIndex = base.index(i.first, offsetBy: distance)
221+
let secondOffsetIndex = base.index(after: firstOffsetIndex)
222+
return secondOffsetIndex == base.endIndex
223+
? endIndex
224+
: Index(first: firstOffsetIndex, second: secondOffsetIndex)
225+
} else {
226+
return i == endIndex
227+
? Index(first: base.index(i.first, offsetBy: distance - 1),
228+
second: base.index(i.first, offsetBy: distance))
229+
: Index(first: base.index(i.first, offsetBy: distance),
230+
second: i.first)
231+
}
232+
}
233+
234+
@inlinable
235+
public func distance(from start: Index, to end: Index) -> Int {
236+
let offset: Int
237+
switch (start.first, end.first) {
238+
case (base.endIndex, base.endIndex):
239+
return 0
240+
case (base.endIndex, _):
241+
offset = +1
242+
case (_, base.endIndex):
243+
offset = -1
244+
default:
245+
offset = 0
246+
}
247+
248+
return base.distance(from: start.first, to: end.first) + offset
249+
}
250+
251+
@inlinable
252+
public var count: Int {
253+
Swift.max(0, base.count - 1)
254+
}
255+
}
256+
257+
extension AdjacentPairsCollection: BidirectionalCollection
258+
where Base: BidirectionalCollection
259+
{
260+
@inlinable
261+
public func index(before i: Index) -> Index {
262+
i == endIndex
263+
? Index(first: base.index(i.first, offsetBy: -2),
264+
second: base.index(before: i.first))
265+
: Index(first: base.index(before: i.first),
266+
second: i.first)
267+
}
268+
}
269+
270+
extension AdjacentPairsCollection: RandomAccessCollection
271+
where Base: RandomAccessCollection {}

0 commit comments

Comments
 (0)