-
Notifications
You must be signed in to change notification settings - Fork 440
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Tim Vermeulen
authored
Jun 1, 2021
1 parent
0e2941e
commit 4b779b8
Showing
7 changed files
with
1,692 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
# Joined | ||
|
||
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Joined.swift) | | ||
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/JoinedTests.swift)] | ||
|
||
Concatenate a sequence of sequences, inserting a separator between each element. | ||
|
||
The separator can be either a single element or a sequence of elements, and it | ||
can optionally depend on the sequences right before and after it by returning it | ||
from a closure: | ||
|
||
```swift | ||
for number in [[1], [2, 3], [4, 5, 6]].joined(by: 100) { | ||
print(number) | ||
} | ||
// 1, 100, 2, 3, 100, 4, 5, 6 | ||
|
||
for number in [[10], [20, 30], [40, 50, 60]].joined(by: { [$0.count, $1.count] }) { | ||
print(number) | ||
} | ||
// 10, 1, 2, 20, 30, 2, 3, 40, 50, 60 | ||
``` | ||
|
||
## Detailed Design | ||
|
||
The versions that take a closure are executed eagerly and are defined on | ||
`Sequence`: | ||
|
||
```swift | ||
extension Sequence where Element: Sequence { | ||
public func joined( | ||
by separator: (Element, Element) throws -> Element.Element | ||
) rethrows -> [Element.Element] | ||
|
||
public func joined<Separator>( | ||
by separator: (Element, Element) throws -> Separator | ||
) rethrows -> [Element.Element] | ||
where Separator: Sequence, Separator.Element == Element.Element | ||
} | ||
``` | ||
|
||
The versions that do not take a closure are defined on both `Sequence` and | ||
`Collection` because the resulting collections need to precompute their start | ||
index to ensure O(1) access: | ||
|
||
```swift | ||
extension Sequence where Element: Sequence { | ||
public func joined(by separator: Element.Element) | ||
-> JoinedBySequence<Self, CollectionOfOne<Element.Element>> | ||
|
||
public func joined<Separator>( | ||
by separator: Separator | ||
) -> JoinedBySequence<Self, Separator> | ||
where Separator: Collection, Separator.Element == Element.Element | ||
} | ||
|
||
extension Collection where Element: Sequence { | ||
public func joined(by separator: Element.Element) | ||
-> JoinedByCollection<Self, CollectionOfOne<Element.Element>> | ||
|
||
public func joined<Separator>( | ||
by separator: Separator | ||
) -> JoinedByCollection<Self, Separator> | ||
where Separator: Collection, Separator.Element == Element.Element | ||
} | ||
``` | ||
|
||
Note that the sequence separator of the closure-less version defined on | ||
`Sequence` is required to be a `Collection`, because a plain `Sequence` cannot in | ||
general be iterated over multiple times. | ||
|
||
The closure-based versions also have lazy variants that are defined on both | ||
`LazySequenceProtocol` and `LazyCollectionProtocol` for the same reason as | ||
explained above: | ||
|
||
```swift | ||
extension LazySequenceProtocol where Element: Sequence { | ||
public func joined( | ||
by separator: @escaping (Element, Element) -> Element.Element | ||
) -> JoinedByClosureSequence<Self, CollectionOfOne<Element.Element>> | ||
|
||
public func joined<Separator>( | ||
by separator: @escaping (Element, Element) -> Separator | ||
) -> JoinedByClosureSequence<Self, Separator> | ||
} | ||
|
||
extension LazyCollectionProtocol where Element: Collection { | ||
public func joined( | ||
by separator: @escaping (Element, Element) -> Element.Element | ||
) -> JoinedByClosureCollection<Self, CollectionOfOne<Element.Element>> | ||
|
||
public func joined<Separator>( | ||
by separator: @escaping (Element, Element) -> Separator | ||
) -> JoinedByClosureCollection<Self, Separator> | ||
} | ||
``` | ||
|
||
`JoinedBySequence`, `JoinedByClosureSequence`, `JoinedByCollection`, and | ||
`JoinedByClosureCollection` conform to `LazySequenceProtocol` when the base | ||
sequence conforms. `JoinedByCollection` and `JoinedByClosureCollection` also | ||
conform to `LazyCollectionProtocol` and `BidirectionalCollection` when the base | ||
collection conforms. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Algorithms open source project | ||
// | ||
// Copyright (c) 2021 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See https://swift.org/LICENSE.txt for license information | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
//===----------------------------------------------------------------------===// | ||
// Either | ||
//===----------------------------------------------------------------------===// | ||
|
||
/// A general-purpose sum type. | ||
@usableFromInline | ||
internal enum Either<Left, Right> { | ||
case left(Left) | ||
case right(Right) | ||
} | ||
|
||
extension Either: Equatable where Left: Equatable, Right: Equatable { | ||
@usableFromInline | ||
internal static func == (lhs: Self, rhs: Self) -> Bool { | ||
switch (lhs, rhs) { | ||
case let (.left(lhs), .left(rhs)): | ||
return lhs == rhs | ||
case let (.right(lhs), .right(rhs)): | ||
return lhs == rhs | ||
case (.left, .right), (.right, .left): | ||
return false | ||
} | ||
} | ||
} | ||
|
||
extension Either: Comparable where Left: Comparable, Right: Comparable { | ||
@usableFromInline | ||
internal static func < (lhs: Self, rhs: Self) -> Bool { | ||
switch (lhs, rhs) { | ||
case let (.left(lhs), .left(rhs)): | ||
return lhs < rhs | ||
case let (.right(lhs), .right(rhs)): | ||
return lhs < rhs | ||
case (.left, .right): | ||
return true | ||
case (.right, .left): | ||
return false | ||
} | ||
} | ||
} | ||
|
||
//===----------------------------------------------------------------------===// | ||
// EitherSequence | ||
//===----------------------------------------------------------------------===// | ||
|
||
/// A sequence that has one of the two specified types. | ||
@usableFromInline | ||
internal enum EitherSequence<Left: Sequence, Right: Sequence> | ||
where Left.Element == Right.Element | ||
{ | ||
case left(Left) | ||
case right(Right) | ||
} | ||
|
||
extension EitherSequence: Sequence { | ||
@usableFromInline | ||
internal struct Iterator: IteratorProtocol { | ||
@usableFromInline | ||
internal var left: Left.Iterator? | ||
|
||
@usableFromInline | ||
internal var right: Right.Iterator? | ||
|
||
@inlinable | ||
internal mutating func next() -> Left.Element? { | ||
left?.next() ?? right?.next() | ||
} | ||
} | ||
|
||
@usableFromInline | ||
internal func makeIterator() -> Iterator { | ||
switch self { | ||
case .left(let left): | ||
return Iterator(left: left.makeIterator(), right: nil) | ||
case .right(let right): | ||
return Iterator(left: nil, right: right.makeIterator()) | ||
} | ||
} | ||
} | ||
|
||
extension EitherSequence: Collection | ||
where Left: Collection, Right: Collection, Left.Element == Right.Element | ||
{ | ||
@usableFromInline | ||
internal typealias Index = Either<Left.Index, Right.Index> | ||
|
||
@inlinable | ||
internal var startIndex: Index { | ||
switch self { | ||
case .left(let s): | ||
return .left(s.startIndex) | ||
case .right(let s): | ||
return .right(s.startIndex) | ||
} | ||
} | ||
|
||
@inlinable | ||
internal var endIndex: Index { | ||
switch self { | ||
case .left(let s): | ||
return .left(s.endIndex) | ||
case .right(let s): | ||
return .right(s.endIndex) | ||
} | ||
} | ||
|
||
@inlinable | ||
internal subscript(position: Index) -> Element { | ||
switch (self, position) { | ||
case let (.left(s), .left(i)): | ||
return s[i] | ||
case let (.right(s), .right(i)): | ||
return s[i] | ||
default: | ||
fatalError() | ||
} | ||
} | ||
|
||
@inlinable | ||
internal func index(after i: Index) -> Index { | ||
switch (self,i) { | ||
case let (.left(s), .left(i)): | ||
return .left(s.index(after: i)) | ||
case let (.right(s), .right(i)): | ||
return .right(s.index(after: i)) | ||
default: | ||
fatalError() | ||
} | ||
} | ||
|
||
@inlinable | ||
internal func index( | ||
_ i: Index, | ||
offsetBy distance: Int, | ||
limitedBy limit: Index | ||
) -> Index? { | ||
switch (self, i, limit) { | ||
case let (.left(s), .left(i), .left(limit)): | ||
return s.index(i, offsetBy: distance, limitedBy: limit).map { .left($0) } | ||
case let (.right(s), .right(i), .right(limit)): | ||
return s.index(i, offsetBy: distance, limitedBy: limit).map { .right($0) } | ||
default: | ||
fatalError() | ||
} | ||
} | ||
|
||
@inlinable | ||
internal func index(_ i: Index, offsetBy distance: Int) -> Index { | ||
switch (self, i) { | ||
case let (.left(s), .left(i)): | ||
return .left(s.index(i, offsetBy: distance)) | ||
case let (.right(s), .right(i)): | ||
return .right(s.index(i, offsetBy: distance)) | ||
default: | ||
fatalError() | ||
} | ||
} | ||
|
||
@inlinable | ||
internal func distance(from start: Index, to end: Index) -> Int { | ||
switch (self, start, end) { | ||
case let (.left(s), .left(i), .left(j)): | ||
return s.distance(from: i, to: j) | ||
case let (.right(s), .right(i), .right(j)): | ||
return s.distance(from: i, to: j) | ||
default: | ||
fatalError() | ||
} | ||
} | ||
} | ||
|
||
extension EitherSequence: BidirectionalCollection | ||
where Left: BidirectionalCollection, Right: BidirectionalCollection | ||
{ | ||
@inlinable | ||
internal func index(before i: Index) -> Index { | ||
switch (self, i) { | ||
case let (.left(s), .left(i)): | ||
return .left(s.index(before: i)) | ||
case let (.right(s), .right(i)): | ||
return .right(s.index(before: i)) | ||
default: | ||
fatalError() | ||
} | ||
} | ||
} | ||
|
||
extension EitherSequence: RandomAccessCollection | ||
where Left: RandomAccessCollection, Right: RandomAccessCollection {} |
Oops, something went wrong.