-
Notifications
You must be signed in to change notification settings - Fork 449
Add interspersed(with:) #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
737aca5
4440add
c4647c9
206bc1e
14629c8
b2b6673
1b5e3ec
f455043
eb28264
5cdbf8b
fe6d16e
ca1a82d
172332e
ac9848c
faf4755
6f6768b
d4bdbc4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Intersperse | ||
|
||
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Intersperse.swift) | | ||
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/IntersperseTests.swift)] | ||
|
||
Place a given value in between each element of the sequence. | ||
|
||
```swift | ||
let numbers = [1, 2, 3].interspersed(with: 0) | ||
// Array(numbers) == [1, 0, 2, 0, 3] | ||
|
||
let letters = "ABCDE".interspersed(with: "-") | ||
// String(letters) == "A-B-C-D-E" | ||
|
||
let empty = [].interspersed(with: 0) | ||
// Array(empty) == [] | ||
``` | ||
|
||
`interspersed(with:)` takes a separator value and inserts it in between every | ||
element in the sequence. | ||
|
||
## Detailed Design | ||
|
||
A new method is added to sequence: | ||
|
||
```swift | ||
extension Sequence { | ||
func interspersed(with separator: Element) -> Intersperse<Self> | ||
} | ||
``` | ||
|
||
The new `Intersperse` type represents the sequence when the separator is | ||
inserted between each element. Intersperse conforms to Collection and | ||
BidirectionalCollection when the base sequence conforms to Collection and | ||
BidirectionalCollection respectively. | ||
|
||
### Complexity | ||
|
||
Calling these methods is O(_1_). | ||
|
||
### Naming | ||
|
||
This method’s and type’s name match the term of art used in other languages | ||
and libraries. | ||
|
||
### Comparison with other languages | ||
|
||
**[Haskell][Haskell]:** Has an `intersperse` function which takes an element | ||
and a list and 'intersperses' that element between the elements of the list. | ||
|
||
**[Rust][Rust]:** Has a function called `intersperse` to insert a particular | ||
value between each element. | ||
|
||
<!-- Link references for other languages --> | ||
|
||
[Haskell]: https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html#v:intersperse | ||
[Rust]: https://docs.rs/itertools/0.9.0/itertools/trait.Itertools.html#method.intersperse |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Algorithms open source project | ||
// | ||
// Copyright (c) 2020 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
/// A sequence that presents the elements of a base sequence of elements | ||
/// with a separator between each of those elements. | ||
public struct Intersperse<Base: Sequence> { | ||
let base: Base | ||
let separator: Base.Element | ||
} | ||
|
||
extension Intersperse: Sequence { | ||
/// The iterator for an `Intersperse` sequence. | ||
public struct Iterator: IteratorProtocol { | ||
var iterator: Base.Iterator | ||
let separator: Base.Element | ||
var state = State.start | ||
|
||
enum State { | ||
case start | ||
case element(Base.Element) | ||
case separator | ||
} | ||
|
||
public mutating func next() -> Base.Element? { | ||
// After the start, the state flips between element and separator. Before | ||
// returning a separator, a check is made for the next element as a | ||
// separator is only returned between two elements. The next element is | ||
// stored to allow it to be returned in the next iteration. | ||
switch state { | ||
case .start: | ||
state = .separator | ||
return iterator.next() | ||
case .separator: | ||
guard let next = iterator.next() else { return nil } | ||
state = .element(next) | ||
return separator | ||
case .element(let element): | ||
state = .separator | ||
return element | ||
} | ||
} | ||
} | ||
|
||
public func makeIterator() -> Intersperse<Base>.Iterator { | ||
Iterator(iterator: base.makeIterator(), separator: separator) | ||
} | ||
} | ||
|
||
extension Intersperse: Collection where Base: Collection { | ||
public struct Index: Comparable { | ||
enum Representation: Equatable { | ||
case element(Base.Index) | ||
case separator(next: Base.Index) | ||
} | ||
let representation: Representation | ||
|
||
public static func < (lhs: Index, rhs: Index) -> Bool { | ||
switch (lhs.representation, rhs.representation) { | ||
case let (.element(li), .element(ri)), | ||
let (.separator(next: li), .separator(next: ri)), | ||
let (.element(li), .separator(next: ri)): | ||
return li < ri | ||
case let (.separator(next: li), .element(ri)): | ||
return li <= ri | ||
} | ||
} | ||
|
||
static func element(_ index: Base.Index) -> Self { | ||
Self(representation: .element(index)) | ||
} | ||
|
||
static func separator(next: Base.Index) -> Self { | ||
Self(representation: .separator(next: next)) | ||
} | ||
} | ||
|
||
public var startIndex: Index { | ||
base.startIndex == base.endIndex ? endIndex : .element(base.startIndex) | ||
} | ||
|
||
public var endIndex: Index { | ||
.separator(next: base.endIndex) | ||
} | ||
|
||
public func index(after i: Index) -> Index { | ||
precondition(i != endIndex, "Can't advance past endIndex") | ||
switch i.representation { | ||
case let .element(index): | ||
return .separator(next: base.index(after: index)) | ||
case let .separator(next): | ||
return .element(next) | ||
} | ||
} | ||
|
||
public subscript(position: Index) -> Element { | ||
switch position.representation { | ||
case .element(let index): return base[index] | ||
case .separator: return separator | ||
} | ||
} | ||
|
||
public func index(_ i: Index, offsetBy distance: Int) -> Index { | ||
switch (i.representation, distance.isMultiple(of: 2)) { | ||
case (let .element(index), true): | ||
return .element(base.index(index, offsetBy: distance / 2)) | ||
case (let .element(index), false): | ||
return .separator(next: base.index(index, offsetBy: (distance + 1) / 2)) | ||
case (let .separator(next: index), true): | ||
return .separator(next: base.index(index, offsetBy: distance / 2)) | ||
case (let .separator(next: index), false): | ||
return .element(base.index(index, offsetBy: (distance - 1) / 2)) | ||
} | ||
} | ||
|
||
// TODO: Implement index(_:offsetBy:limitedBy:) | ||
|
||
public func distance(from start: Index, to end: Index) -> Int { | ||
switch (start.representation, end.representation) { | ||
case let (.element(element), .separator(next: separator)): | ||
return 2 * base.distance(from: element, to: separator) - 1 | ||
case let (.separator(next: separator), .element(element)): | ||
return 2 * base.distance(from: separator, to: element) + 1 | ||
case let (.element(start), .element(end)), | ||
let (.separator(start), .separator(end)): | ||
return 2 * base.distance(from: start, to: end) | ||
} | ||
} | ||
} | ||
|
||
extension Intersperse: BidirectionalCollection | ||
where Base: BidirectionalCollection | ||
{ | ||
public func index(before i: Index) -> Index { | ||
precondition(i != startIndex, "Can't move before startIndex") | ||
switch i.representation { | ||
case let .element(index): | ||
return .separator(next: index) | ||
case let .separator(next): | ||
return .element(base.index(before: next)) | ||
} | ||
} | ||
} | ||
|
||
extension Intersperse: RandomAccessCollection | ||
where Base: RandomAccessCollection {} | ||
|
||
extension Sequence { | ||
|
||
/// Returns a sequence containing elements of this sequence with the given | ||
/// separator inserted in between each element. | ||
/// | ||
/// Any value of the sequence's element type can be used as the separator. | ||
/// | ||
/// ``` | ||
/// for value in [1,2,3].interspersed(with: 0) { | ||
/// print(value) | ||
/// } | ||
/// // 1 | ||
/// // 0 | ||
/// // 2 | ||
/// // 0 | ||
/// // 3 | ||
/// ``` | ||
/// | ||
/// The following shows a String being interspersed with a Character: | ||
/// ``` | ||
/// let result = "ABCDE".interspersed(with: "-") | ||
/// print(String(result)) | ||
/// // "A-B-C-D-E" | ||
/// ``` | ||
/// | ||
/// - Parameter separator: Value to insert in between each of this sequence’s | ||
/// elements. | ||
/// - Returns: The interspersed sequence of elements. | ||
/// | ||
/// - Complexity: O(1) | ||
public func interspersed(with separator: Element) -> Intersperse<Self> { | ||
Intersperse(base: self, separator: separator) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
//===----------------------------------------------------------------------===// | ||
// | ||
// This source file is part of the Swift Algorithms open source project | ||
// | ||
// Copyright (c) 2020 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 | ||
// | ||
//===----------------------------------------------------------------------===// | ||
|
||
import XCTest | ||
import Algorithms | ||
|
||
final class IntersperseTests: XCTestCase { | ||
func testSequence() { | ||
let interspersed = (1...).prefix(5).interspersed(with: 0) | ||
XCTAssertEqualSequences(interspersed, [1,0,2,0,3,0,4,0,5]) | ||
} | ||
|
||
func testSequenceEmpty() { | ||
let interspersed = (1...).prefix(0).interspersed(with: 0) | ||
XCTAssertEqualSequences(interspersed, []) | ||
} | ||
|
||
func testString() { | ||
let interspersed = "ABCDE".interspersed(with: "-") | ||
XCTAssertEqualSequences(interspersed, "A-B-C-D-E") | ||
validateIndexTraversals(interspersed) | ||
} | ||
|
||
func testStringEmpty() { | ||
let interspersed = "".interspersed(with: "-") | ||
XCTAssertEqualSequences(interspersed, "") | ||
validateIndexTraversals(interspersed) | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you add a test that validates against empty and non-empty sequences? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah that makes sense, so that it doesn't go through any of the Collection code? If I've understood correctly, this is in d4bdbc4. 🙂 |
||
|
||
func testArray() { | ||
let interspersed = [1,2,3,4].interspersed(with: 0) | ||
XCTAssertEqualSequences(interspersed, [1,0,2,0,3,0,4]) | ||
validateIndexTraversals(interspersed) | ||
} | ||
|
||
func testArrayEmpty() { | ||
let interspersed = [].interspersed(with: 0) | ||
XCTAssertEqualSequences(interspersed, []) | ||
validateIndexTraversals(interspersed) | ||
} | ||
|
||
func testCollection() { | ||
let interspersed = ["A","B","C","D"].interspersed(with: "-") | ||
XCTAssertEqual(interspersed.count, 7) | ||
} | ||
|
||
func testBidirectionalCollection() { | ||
let reversed = "ABCDE".interspersed(with: "-").reversed() | ||
XCTAssertEqualSequences(reversed, "E-D-C-B-A") | ||
validateIndexTraversals(reversed) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns an invalid index for
c.index(after: c.endIndex)
— can you add a precondition for that case?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch,
index(before:)
also had the same issue. Implemented in 6f6768b.