Skip to content

Commit 5a192f2

Browse files
committed
Add guide for Overlay, rename files to Overlay/OverlayTests
1 parent 2f54c4f commit 5a192f2

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

Guides/Overlay.md

+170
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Overlay
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Overlay.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/OverlayTests.swift)]
5+
6+
Compose collections by overlaying the elements of one collection
7+
over an arbitrary region of another collection.
8+
9+
Swift offers many interesting collections, for instance:
10+
11+
- `Range<Int>` allows us to express the numbers in `0..<1000`
12+
in an efficient way that does not allocate storage for each number.
13+
14+
- `Repeated<Int>` allows us to express, say, one thousand copies of the same value,
15+
without allocating space for a thousand values.
16+
17+
- `LazyMapCollection` allows us to transform the elements of a collection on-demand,
18+
without creating a copy of the source collection and eagerly transforming every element.
19+
20+
- The collections in this package, such as `.chunked`, `.cycled`, `.joined`, and `.interspersed`,
21+
similarly compute their elements on-demand.
22+
23+
While these collections can be very efficient, it is difficult to compose them in to arbitrary datasets.
24+
If we have the Range `5..<10`, and want to insert a `0` in the middle of it, we would need to allocate storage
25+
for the entire collection, losing the benefits of `Range<Int>`. Similarly, if we have some numbers in storage
26+
(say, in an Array) and wish to insert a contiguous range in the middle of it, we have to allocate storage
27+
in the Array and cannot take advantage of `Range<Int>` memory efficiency.
28+
29+
The `OverlayCollection` allows us to form arbitrary compositions without mutating
30+
or allocating storage for the result.
31+
32+
```swift
33+
// 'numbers' is a composition of:
34+
// - Range<Int>, and
35+
// - CollectionOfOne<Int>
36+
37+
let numbers = (5..<10).overlay.inserting(0, at: 7)
38+
39+
for n in numbers {
40+
// n: 5, 6, 0, 7, 8, 9
41+
// ^
42+
}
43+
```
44+
45+
```swift
46+
// 'numbers' is a composition of:
47+
// - Array<Int>, and
48+
// - Range<Int>
49+
50+
let rawdata = [3, 6, 1, 4, 6]
51+
let numbers = rawdata.overlay.inserting(contentsOf: 5..<10, at: 3)
52+
53+
for n in numbers {
54+
// n: 3, 6, 1, 5, 6, 7, 8, 9, 4, 6
55+
// ^^^^^^^^^^^^^
56+
}
57+
```
58+
59+
We can also insert elements in to a `LazyMapCollection`:
60+
61+
```swift
62+
enum ListItem {
63+
case product(Product)
64+
case callToAction
65+
}
66+
67+
let products: [Product] = ...
68+
69+
var listItems: some Collection<ListItem> {
70+
products
71+
.lazy.map { ListItem.product($0) }
72+
.overlay.inserting(.callToAction, at: min(4, products.count))
73+
}
74+
75+
for item in listItems {
76+
// item: .product(A), .product(B), .product(C), .callToAction, .product(D), ...
77+
// ^^^^^^^^^^^^^
78+
}
79+
```
80+
81+
## Detailed Design
82+
83+
An `.overlay` member is added to all collections:
84+
85+
```swift
86+
extension Collection {
87+
public var overlay: OverlayCollectionNamespace<Self> { get }
88+
}
89+
```
90+
91+
This member returns a wrapper structure, `OverlayCollectionNamespace`,
92+
which provides a similar suite of methods to the standard library's `RangeReplaceableCollection` protocol.
93+
94+
However, while `RangeReplaceableCollection` methods mutate the collection they are applied to,
95+
these methods return a new `OverlayCollection` value which substitutes the specified elements on-demand.
96+
97+
```swift
98+
extension OverlayCollectionNamespace {
99+
100+
// Multiple elements:
101+
102+
public func replacingSubrange<Overlay>(
103+
_ subrange: Range<Elements.Index>, with newElements: Overlay
104+
) -> OverlayCollection<Elements, Overlay>
105+
106+
public func appending<Overlay>(
107+
contentsOf newElements: Overlay
108+
) -> OverlayCollection<Elements, Overlay>
109+
110+
public func inserting<Overlay>(
111+
contentsOf newElements: Overlay, at position: Elements.Index
112+
) -> OverlayCollection<Elements, Overlay>
113+
114+
public func removingSubrange(
115+
_ subrange: Range<Elements.Index>
116+
) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>>
117+
118+
// Single elements:
119+
120+
public func appending(
121+
_ element: Elements.Element
122+
) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>>
123+
124+
public func inserting(
125+
_ element: Elements.Element, at position: Elements.Index
126+
) -> OverlayCollection<Elements, CollectionOfOne<Elements.Element>>
127+
128+
public func removing(
129+
at position: Elements.Index
130+
) -> OverlayCollection<Elements, EmptyCollection<Elements.Element>>
131+
132+
}
133+
```
134+
135+
`OverlayCollection` conforms to `BidirectionalCollection` when both the base and overlay collections conform.
136+
137+
### Conditional Overlays
138+
139+
In order to allow overlays to be applied conditionally, another function is added to all collections:
140+
141+
```swift
142+
extension Collection {
143+
144+
public func overlay<Overlay>(
145+
if condition: Bool,
146+
_ makeOverlay: (OverlayCollectionNamespace<Self>) -> OverlayCollection<Self, Overlay>
147+
) -> OverlayCollection<Self, Overlay>
148+
149+
}
150+
```
151+
152+
If the `condition` parameter is `true`, the `makeOverlay` closure is invoked to apply the desired overlay.
153+
If `condition` is `false`, the closure is not invoked, and the function returns a no-op overlay,
154+
containing the same elements as the base collection.
155+
156+
This allows overlays to be applied conditionally while still being usable as opaque return types:
157+
158+
```swift
159+
func getNumbers(shouldInsert: Bool) -> some Collection<Int> {
160+
(5..<10).overlay(if: shouldInsert) { $0.inserting(0, at: 7) }
161+
}
162+
163+
for n in getNumbers(shouldInsert: true) {
164+
// n: 5, 6, 0, 7, 8, 9
165+
}
166+
167+
for n in getNumbers(shouldInsert: false) {
168+
// n: 5, 6, 7, 8, 9
169+
}
170+
```
File renamed without changes.

0 commit comments

Comments
 (0)