Skip to content

Commit 4b779b8

Browse files
author
Tim Vermeulen
authored
Add joined(by:) (apple#138)
1 parent 0e2941e commit 4b779b8

File tree

7 files changed

+1692
-20
lines changed

7 files changed

+1692
-20
lines changed

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.
+200
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
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+
//===----------------------------------------------------------------------===//
13+
// Either
14+
//===----------------------------------------------------------------------===//
15+
16+
/// A general-purpose sum type.
17+
@usableFromInline
18+
internal enum Either<Left, Right> {
19+
case left(Left)
20+
case right(Right)
21+
}
22+
23+
extension Either: Equatable where Left: Equatable, Right: Equatable {
24+
@usableFromInline
25+
internal static func == (lhs: Self, rhs: Self) -> Bool {
26+
switch (lhs, rhs) {
27+
case let (.left(lhs), .left(rhs)):
28+
return lhs == rhs
29+
case let (.right(lhs), .right(rhs)):
30+
return lhs == rhs
31+
case (.left, .right), (.right, .left):
32+
return false
33+
}
34+
}
35+
}
36+
37+
extension Either: Comparable where Left: Comparable, Right: Comparable {
38+
@usableFromInline
39+
internal static func < (lhs: Self, rhs: Self) -> Bool {
40+
switch (lhs, rhs) {
41+
case let (.left(lhs), .left(rhs)):
42+
return lhs < rhs
43+
case let (.right(lhs), .right(rhs)):
44+
return lhs < rhs
45+
case (.left, .right):
46+
return true
47+
case (.right, .left):
48+
return false
49+
}
50+
}
51+
}
52+
53+
//===----------------------------------------------------------------------===//
54+
// EitherSequence
55+
//===----------------------------------------------------------------------===//
56+
57+
/// A sequence that has one of the two specified types.
58+
@usableFromInline
59+
internal enum EitherSequence<Left: Sequence, Right: Sequence>
60+
where Left.Element == Right.Element
61+
{
62+
case left(Left)
63+
case right(Right)
64+
}
65+
66+
extension EitherSequence: Sequence {
67+
@usableFromInline
68+
internal struct Iterator: IteratorProtocol {
69+
@usableFromInline
70+
internal var left: Left.Iterator?
71+
72+
@usableFromInline
73+
internal var right: Right.Iterator?
74+
75+
@inlinable
76+
internal mutating func next() -> Left.Element? {
77+
left?.next() ?? right?.next()
78+
}
79+
}
80+
81+
@usableFromInline
82+
internal func makeIterator() -> Iterator {
83+
switch self {
84+
case .left(let left):
85+
return Iterator(left: left.makeIterator(), right: nil)
86+
case .right(let right):
87+
return Iterator(left: nil, right: right.makeIterator())
88+
}
89+
}
90+
}
91+
92+
extension EitherSequence: Collection
93+
where Left: Collection, Right: Collection, Left.Element == Right.Element
94+
{
95+
@usableFromInline
96+
internal typealias Index = Either<Left.Index, Right.Index>
97+
98+
@inlinable
99+
internal var startIndex: Index {
100+
switch self {
101+
case .left(let s):
102+
return .left(s.startIndex)
103+
case .right(let s):
104+
return .right(s.startIndex)
105+
}
106+
}
107+
108+
@inlinable
109+
internal var endIndex: Index {
110+
switch self {
111+
case .left(let s):
112+
return .left(s.endIndex)
113+
case .right(let s):
114+
return .right(s.endIndex)
115+
}
116+
}
117+
118+
@inlinable
119+
internal subscript(position: Index) -> Element {
120+
switch (self, position) {
121+
case let (.left(s), .left(i)):
122+
return s[i]
123+
case let (.right(s), .right(i)):
124+
return s[i]
125+
default:
126+
fatalError()
127+
}
128+
}
129+
130+
@inlinable
131+
internal func index(after i: Index) -> Index {
132+
switch (self,i) {
133+
case let (.left(s), .left(i)):
134+
return .left(s.index(after: i))
135+
case let (.right(s), .right(i)):
136+
return .right(s.index(after: i))
137+
default:
138+
fatalError()
139+
}
140+
}
141+
142+
@inlinable
143+
internal func index(
144+
_ i: Index,
145+
offsetBy distance: Int,
146+
limitedBy limit: Index
147+
) -> Index? {
148+
switch (self, i, limit) {
149+
case let (.left(s), .left(i), .left(limit)):
150+
return s.index(i, offsetBy: distance, limitedBy: limit).map { .left($0) }
151+
case let (.right(s), .right(i), .right(limit)):
152+
return s.index(i, offsetBy: distance, limitedBy: limit).map { .right($0) }
153+
default:
154+
fatalError()
155+
}
156+
}
157+
158+
@inlinable
159+
internal func index(_ i: Index, offsetBy distance: Int) -> Index {
160+
switch (self, i) {
161+
case let (.left(s), .left(i)):
162+
return .left(s.index(i, offsetBy: distance))
163+
case let (.right(s), .right(i)):
164+
return .right(s.index(i, offsetBy: distance))
165+
default:
166+
fatalError()
167+
}
168+
}
169+
170+
@inlinable
171+
internal func distance(from start: Index, to end: Index) -> Int {
172+
switch (self, start, end) {
173+
case let (.left(s), .left(i), .left(j)):
174+
return s.distance(from: i, to: j)
175+
case let (.right(s), .right(i), .right(j)):
176+
return s.distance(from: i, to: j)
177+
default:
178+
fatalError()
179+
}
180+
}
181+
}
182+
183+
extension EitherSequence: BidirectionalCollection
184+
where Left: BidirectionalCollection, Right: BidirectionalCollection
185+
{
186+
@inlinable
187+
internal func index(before i: Index) -> Index {
188+
switch (self, i) {
189+
case let (.left(s), .left(i)):
190+
return .left(s.index(before: i))
191+
case let (.right(s), .right(i)):
192+
return .right(s.index(before: i))
193+
default:
194+
fatalError()
195+
}
196+
}
197+
}
198+
199+
extension EitherSequence: RandomAccessCollection
200+
where Left: RandomAccessCollection, Right: RandomAccessCollection {}

0 commit comments

Comments
 (0)