Skip to content

Commit e3dea6f

Browse files
author
Tim Vermeulen
committed
Add guide, doc comments, tests
1 parent 99227df commit e3dea6f

File tree

7 files changed

+346
-36
lines changed

7 files changed

+346
-36
lines changed

Diff for: Guides/Joined.md

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Joined
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Joined.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/JoinedTests.swift)]
5+
6+
Concatenate a sequence of sequences, inserting a separator between each element.
7+
8+
The separator can be either a single element or a sequence of elements, and it
9+
can optionally depend on the sequences right before and after it by returning it
10+
from a closure:
11+
12+
```swift
13+
for number in [[1], [2, 3], [4, 5, 6]].joined(by: 100) {
14+
print(number)
15+
}
16+
// 1, 100, 2, 3, 100, 4, 5, 6
17+
18+
for number in [[10], [20, 30], [40, 50, 60]].joined(by: { [$0.count, $1.count] }) {
19+
print(number)
20+
}
21+
// 10, 1, 2, 20, 30, 2, 3, 40, 50, 60
22+
```
23+
24+
## Detailed Design
25+
26+
The versions that take a closure are executed eagerly and are defined on
27+
`Sequence`:
28+
29+
```swift
30+
extension Sequence where Element: Sequence {
31+
public func joined(
32+
by separator: (Element, Element) throws -> Element.Element
33+
) rethrows -> [Element.Element]
34+
35+
public func joined<Separator>(
36+
by separator: (Element, Element) throws -> Separator
37+
) rethrows -> [Element.Element]
38+
where Separator: Sequence, Separator.Element == Element.Element
39+
}
40+
```
41+
42+
The versions that do not take a closure are defined on both `Sequence` and
43+
`Collection` because the resulting collections need to precompute their start
44+
index to ensure O(1) access:
45+
46+
```swift
47+
extension Sequence where Element: Sequence {
48+
public func joined(by separator: Element.Element)
49+
-> JoinedBySequence<Self, CollectionOfOne<Element.Element>>
50+
51+
public func joined<Separator>(
52+
by separator: Separator
53+
) -> JoinedBySequence<Self, Separator>
54+
where Separator: Collection, Separator.Element == Element.Element
55+
}
56+
57+
extension Collection where Element: Sequence {
58+
public func joined(by separator: Element.Element)
59+
-> JoinedByCollection<Self, CollectionOfOne<Element.Element>>
60+
61+
public func joined<Separator>(
62+
by separator: Separator
63+
) -> JoinedByCollection<Self, Separator>
64+
where Separator: Collection, Separator.Element == Element.Element
65+
}
66+
```
67+
68+
Note that the sequence separator of the closure-less version defined on
69+
`Sequence` is required to be a `Collection`, because a plain `Sequence` cannot in
70+
general be iterated over multiple times.
71+
72+
The closure-based versions also have lazy variants that are defined on both
73+
`LazySequenceProtocol` and `LazyCollectionProtocol` for the same reason as
74+
explained above:
75+
76+
```swift
77+
extension LazySequenceProtocol where Element: Sequence {
78+
public func joined(
79+
by separator: @escaping (Element, Element) -> Element.Element
80+
) -> JoinedByClosureSequence<Self, CollectionOfOne<Element.Element>>
81+
82+
public func joined<Separator>(
83+
by separator: @escaping (Element, Element) -> Separator
84+
) -> JoinedByClosureSequence<Self, Separator>
85+
}
86+
87+
extension LazyCollectionProtocol where Element: Collection {
88+
public func joined(
89+
by separator: @escaping (Element, Element) -> Element.Element
90+
) -> JoinedByClosureCollection<Self, CollectionOfOne<Element.Element>>
91+
92+
public func joined<Separator>(
93+
by separator: @escaping (Element, Element) -> Separator
94+
) -> JoinedByClosureCollection<Self, Separator>
95+
}
96+
```
97+
98+
`JoinedBySequence`, `JoinedByClosureSequence`, `JoinedByCollection`, and
99+
`JoinedByClosureCollection` conform to `LazySequenceProtocol` when the base
100+
sequence conforms. `JoinedByCollection` and `JoinedByClosureCollection` also
101+
conform to `LazyCollectionProtocol` and `BidirectionalCollection` when the base
102+
collection conforms.

Diff for: Sources/Algorithms/EitherSequence.swift

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// Either
1414
//===----------------------------------------------------------------------===//
1515

16+
/// A general-purpose sum type.
1617
@usableFromInline
1718
internal enum Either<Left, Right> {
1819
case left(Left)
@@ -53,6 +54,7 @@ extension Either: Comparable where Left: Comparable, Right: Comparable {
5354
// EitherSequence
5455
//===----------------------------------------------------------------------===//
5556

57+
/// A sequence that has one of the two specified types.
5658
@usableFromInline
5759
internal enum EitherSequence<Left: Sequence, Right: Sequence>
5860
where Left.Element == Right.Element

Diff for: Sources/Algorithms/FlattenCollection.swift

+26-5
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
/// A collection consisting of all the elements contained in a collection of
13+
/// collections.
1214
@usableFromInline
1315
internal struct FlattenCollection<Base: Collection> where Base.Element: Collection {
1416
@usableFromInline
@@ -98,7 +100,8 @@ extension FlattenCollection: Collection {
98100
else { return base[start.outer].distance(from: startInner, to: end.inner!) }
99101

100102
let firstPart = base[start.outer][startInner...].count
101-
let middlePart = base[start.outer..<end.outer].dropFirst().reduce(0, { $0 + $1.count })
103+
let middlePart = base[start.outer..<end.outer].dropFirst()
104+
.reduce(0, { $0 + $1.count })
102105
let lastPart = end.inner.map { base[end.outer][..<$0].count } ?? 0
103106

104107
return firstPart + middlePart + lastPart
@@ -226,7 +229,11 @@ extension FlattenCollection: Collection {
226229
if let indexInner = index.inner {
227230
let element = base[index.outer]
228231

229-
if let inner = element.index(indexInner, offsetBy: -remainder, limitedBy: element.startIndex) {
232+
if let inner = element.index(
233+
indexInner,
234+
offsetBy: -remainder,
235+
limitedBy: element.startIndex
236+
) {
230237
return Index(outer: index.outer, inner: inner)
231238
}
232239

@@ -238,7 +245,11 @@ extension FlattenCollection: Collection {
238245
while outer != limit.outer {
239246
let element = base[outer]
240247

241-
if let inner = element.index(element.endIndex, offsetBy: -remainder, limitedBy: element.startIndex) {
248+
if let inner = element.index(
249+
element.endIndex,
250+
offsetBy: -remainder,
251+
limitedBy: element.startIndex
252+
) {
242253
return Index(outer: outer, inner: inner)
243254
}
244255

@@ -247,8 +258,11 @@ extension FlattenCollection: Collection {
247258
}
248259

249260
let element = base[outer]
250-
return element.index(element.endIndex, offsetBy: -remainder, limitedBy: limit.inner!)
251-
.map { inner in Index(outer: outer, inner: inner) }
261+
return element.index(
262+
element.endIndex,
263+
offsetBy: -remainder,
264+
limitedBy: limit.inner!
265+
).map { inner in Index(outer: outer, inner: inner) }
252266
}
253267
}
254268

@@ -273,11 +287,18 @@ extension FlattenCollection: BidirectionalCollection
273287
}
274288
}
275289

290+
extension FlattenCollection: LazySequenceProtocol
291+
where Base: LazySequenceProtocol, Base.Element: LazySequenceProtocol {}
292+
extension FlattenCollection: LazyCollectionProtocol
293+
where Base: LazyCollectionProtocol, Base.Element: LazyCollectionProtocol {}
294+
276295
//===----------------------------------------------------------------------===//
277296
// joined()
278297
//===----------------------------------------------------------------------===//
279298

280299
extension Collection where Element: Collection {
300+
/// Returns the concatenation of the elements in this collection of
301+
/// collections.
281302
@inlinable
282303
internal func joined() -> FlattenCollection<Self> {
283304
FlattenCollection(base: self)

Diff for: Sources/Algorithms/Intersperse.swift

+27-2
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,10 @@ extension Intersperse: LazyCollectionProtocol
284284
// InterspersedMap
285285
//===----------------------------------------------------------------------===//
286286

287+
288+
/// A sequence over the results of applying a closure to the sequence's
289+
/// elements, with a separator that separates each pair of adjacent transformed
290+
/// values.
287291
@usableFromInline
288292
internal struct InterspersedMap<Base: Sequence, Result> {
289293
@usableFromInline
@@ -360,7 +364,7 @@ extension InterspersedMap: Collection where Base: Collection {
360364
@usableFromInline
361365
internal struct Index: Comparable {
362366
@usableFromInline
363-
internal enum Representation {
367+
internal enum Representation: Equatable {
364368
case element(Base.Index)
365369
case separator(previous: Base.Index, next: Base.Index)
366370
}
@@ -573,6 +577,14 @@ extension InterspersedMap: BidirectionalCollection
573577
}
574578
}
575579

580+
extension InterspersedMap.Index.Representation: Hashable
581+
where Base.Index: Hashable {}
582+
583+
extension InterspersedMap: LazySequenceProtocol
584+
where Base: LazySequenceProtocol {}
585+
extension InterspersedMap: LazyCollectionProtocol
586+
where Base: LazyCollectionProtocol {}
587+
576588
//===----------------------------------------------------------------------===//
577589
// interspersed(with:)
578590
//===----------------------------------------------------------------------===//
@@ -616,8 +628,21 @@ extension Sequence {
616628
//===----------------------------------------------------------------------===//
617629
// lazy.interspersed(_:with:)
618630
//===----------------------------------------------------------------------===//
619-
631+
620632
extension LazySequenceProtocol {
633+
/// Returns a sequence over the results of applying a closure to the
634+
/// sequence's elements, with a separator that separates each pair of adjacent
635+
/// transformed values.
636+
///
637+
/// The transformation closure lets you intersperse a sequence using a
638+
/// separator of a different type than the original's sequence's elements.
639+
/// Each separator is produced by a closure that is given access to the
640+
/// two elements in the original sequence right before and after it.
641+
///
642+
/// let strings = [1, 2, 2].interspersedMap(String.init,
643+
/// with: { $0 == $1 ? " == " : " != " })
644+
/// print(strings.joined()) // "1 != 2 == 2"
645+
///
621646
@usableFromInline
622647
internal func interspersedMap<Result>(
623648
_ transform: @escaping (Element) -> Result,

0 commit comments

Comments
 (0)