Skip to content

Commit fd189aa

Browse files
authored
Add interspersed(with:) (#35)
1 parent 6933a31 commit fd189aa

File tree

3 files changed

+305
-0
lines changed

3 files changed

+305
-0
lines changed

Guides/Intersperse.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Intersperse
2+
3+
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Intersperse.swift) |
4+
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/IntersperseTests.swift)]
5+
6+
Place a given value in between each element of the sequence.
7+
8+
```swift
9+
let numbers = [1, 2, 3].interspersed(with: 0)
10+
// Array(numbers) == [1, 0, 2, 0, 3]
11+
12+
let letters = "ABCDE".interspersed(with: "-")
13+
// String(letters) == "A-B-C-D-E"
14+
15+
let empty = [].interspersed(with: 0)
16+
// Array(empty) == []
17+
```
18+
19+
`interspersed(with:)` takes a separator value and inserts it in between every
20+
element in the sequence.
21+
22+
## Detailed Design
23+
24+
A new method is added to sequence:
25+
26+
```swift
27+
extension Sequence {
28+
func interspersed(with separator: Element) -> Intersperse<Self>
29+
}
30+
```
31+
32+
The new `Intersperse` type represents the sequence when the separator is
33+
inserted between each element. Intersperse conforms to Collection and
34+
BidirectionalCollection when the base sequence conforms to Collection and
35+
BidirectionalCollection respectively.
36+
37+
### Complexity
38+
39+
Calling these methods is O(_1_).
40+
41+
### Naming
42+
43+
This method’s and type’s name match the term of art used in other languages
44+
and libraries.
45+
46+
### Comparison with other languages
47+
48+
**[Haskell][Haskell]:** Has an `intersperse` function which takes an element
49+
and a list and 'intersperses' that element between the elements of the list.
50+
51+
**[Rust][Rust]:** Has a function called `intersperse` to insert a particular
52+
value between each element.
53+
54+
<!-- Link references for other languages -->
55+
56+
[Haskell]: https://hackage.haskell.org/package/base-4.14.0.0/docs/Data-List.html#v:intersperse
57+
[Rust]: https://docs.rs/itertools/0.9.0/itertools/trait.Itertools.html#method.intersperse

Sources/Algorithms/Intersperse.swift

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2020 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+
/// A sequence that presents the elements of a base sequence of elements
13+
/// with a separator between each of those elements.
14+
public struct Intersperse<Base: Sequence> {
15+
let base: Base
16+
let separator: Base.Element
17+
}
18+
19+
extension Intersperse: Sequence {
20+
/// The iterator for an `Intersperse` sequence.
21+
public struct Iterator: IteratorProtocol {
22+
var iterator: Base.Iterator
23+
let separator: Base.Element
24+
var state = State.start
25+
26+
enum State {
27+
case start
28+
case element(Base.Element)
29+
case separator
30+
}
31+
32+
public mutating func next() -> Base.Element? {
33+
// After the start, the state flips between element and separator. Before
34+
// returning a separator, a check is made for the next element as a
35+
// separator is only returned between two elements. The next element is
36+
// stored to allow it to be returned in the next iteration.
37+
switch state {
38+
case .start:
39+
state = .separator
40+
return iterator.next()
41+
case .separator:
42+
guard let next = iterator.next() else { return nil }
43+
state = .element(next)
44+
return separator
45+
case .element(let element):
46+
state = .separator
47+
return element
48+
}
49+
}
50+
}
51+
52+
public func makeIterator() -> Intersperse<Base>.Iterator {
53+
Iterator(iterator: base.makeIterator(), separator: separator)
54+
}
55+
}
56+
57+
extension Intersperse: Collection where Base: Collection {
58+
public struct Index: Comparable {
59+
enum Representation: Equatable {
60+
case element(Base.Index)
61+
case separator(next: Base.Index)
62+
}
63+
let representation: Representation
64+
65+
public static func < (lhs: Index, rhs: Index) -> Bool {
66+
switch (lhs.representation, rhs.representation) {
67+
case let (.element(li), .element(ri)),
68+
let (.separator(next: li), .separator(next: ri)),
69+
let (.element(li), .separator(next: ri)):
70+
return li < ri
71+
case let (.separator(next: li), .element(ri)):
72+
return li <= ri
73+
}
74+
}
75+
76+
static func element(_ index: Base.Index) -> Self {
77+
Self(representation: .element(index))
78+
}
79+
80+
static func separator(next: Base.Index) -> Self {
81+
Self(representation: .separator(next: next))
82+
}
83+
}
84+
85+
public var startIndex: Index {
86+
base.startIndex == base.endIndex ? endIndex : .element(base.startIndex)
87+
}
88+
89+
public var endIndex: Index {
90+
.separator(next: base.endIndex)
91+
}
92+
93+
public func index(after i: Index) -> Index {
94+
precondition(i != endIndex, "Can't advance past endIndex")
95+
switch i.representation {
96+
case let .element(index):
97+
return .separator(next: base.index(after: index))
98+
case let .separator(next):
99+
return .element(next)
100+
}
101+
}
102+
103+
public subscript(position: Index) -> Element {
104+
switch position.representation {
105+
case .element(let index): return base[index]
106+
case .separator: return separator
107+
}
108+
}
109+
110+
public func index(_ i: Index, offsetBy distance: Int) -> Index {
111+
switch (i.representation, distance.isMultiple(of: 2)) {
112+
case (let .element(index), true):
113+
return .element(base.index(index, offsetBy: distance / 2))
114+
case (let .element(index), false):
115+
return .separator(next: base.index(index, offsetBy: (distance + 1) / 2))
116+
case (let .separator(next: index), true):
117+
return .separator(next: base.index(index, offsetBy: distance / 2))
118+
case (let .separator(next: index), false):
119+
return .element(base.index(index, offsetBy: (distance - 1) / 2))
120+
}
121+
}
122+
123+
// TODO: Implement index(_:offsetBy:limitedBy:)
124+
125+
public func distance(from start: Index, to end: Index) -> Int {
126+
switch (start.representation, end.representation) {
127+
case let (.element(element), .separator(next: separator)):
128+
return 2 * base.distance(from: element, to: separator) - 1
129+
case let (.separator(next: separator), .element(element)):
130+
return 2 * base.distance(from: separator, to: element) + 1
131+
case let (.element(start), .element(end)),
132+
let (.separator(start), .separator(end)):
133+
return 2 * base.distance(from: start, to: end)
134+
}
135+
}
136+
}
137+
138+
extension Intersperse: BidirectionalCollection
139+
where Base: BidirectionalCollection
140+
{
141+
public func index(before i: Index) -> Index {
142+
precondition(i != startIndex, "Can't move before startIndex")
143+
switch i.representation {
144+
case let .element(index):
145+
return .separator(next: index)
146+
case let .separator(next):
147+
return .element(base.index(before: next))
148+
}
149+
}
150+
}
151+
152+
extension Intersperse: RandomAccessCollection
153+
where Base: RandomAccessCollection {}
154+
155+
extension Sequence {
156+
157+
/// Returns a sequence containing elements of this sequence with the given
158+
/// separator inserted in between each element.
159+
///
160+
/// Any value of the sequence's element type can be used as the separator.
161+
///
162+
/// ```
163+
/// for value in [1,2,3].interspersed(with: 0) {
164+
/// print(value)
165+
/// }
166+
/// // 1
167+
/// // 0
168+
/// // 2
169+
/// // 0
170+
/// // 3
171+
/// ```
172+
///
173+
/// The following shows a String being interspersed with a Character:
174+
/// ```
175+
/// let result = "ABCDE".interspersed(with: "-")
176+
/// print(String(result))
177+
/// // "A-B-C-D-E"
178+
/// ```
179+
///
180+
/// - Parameter separator: Value to insert in between each of this sequence’s
181+
/// elements.
182+
/// - Returns: The interspersed sequence of elements.
183+
///
184+
/// - Complexity: O(1)
185+
public func interspersed(with separator: Element) -> Intersperse<Self> {
186+
Intersperse(base: self, separator: separator)
187+
}
188+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Algorithms open source project
4+
//
5+
// Copyright (c) 2020 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+
import XCTest
13+
import Algorithms
14+
15+
final class IntersperseTests: XCTestCase {
16+
func testSequence() {
17+
let interspersed = (1...).prefix(5).interspersed(with: 0)
18+
XCTAssertEqualSequences(interspersed, [1,0,2,0,3,0,4,0,5])
19+
}
20+
21+
func testSequenceEmpty() {
22+
let interspersed = (1...).prefix(0).interspersed(with: 0)
23+
XCTAssertEqualSequences(interspersed, [])
24+
}
25+
26+
func testString() {
27+
let interspersed = "ABCDE".interspersed(with: "-")
28+
XCTAssertEqualSequences(interspersed, "A-B-C-D-E")
29+
validateIndexTraversals(interspersed)
30+
}
31+
32+
func testStringEmpty() {
33+
let interspersed = "".interspersed(with: "-")
34+
XCTAssertEqualSequences(interspersed, "")
35+
validateIndexTraversals(interspersed)
36+
}
37+
38+
func testArray() {
39+
let interspersed = [1,2,3,4].interspersed(with: 0)
40+
XCTAssertEqualSequences(interspersed, [1,0,2,0,3,0,4])
41+
validateIndexTraversals(interspersed)
42+
}
43+
44+
func testArrayEmpty() {
45+
let interspersed = [].interspersed(with: 0)
46+
XCTAssertEqualSequences(interspersed, [])
47+
validateIndexTraversals(interspersed)
48+
}
49+
50+
func testCollection() {
51+
let interspersed = ["A","B","C","D"].interspersed(with: "-")
52+
XCTAssertEqual(interspersed.count, 7)
53+
}
54+
55+
func testBidirectionalCollection() {
56+
let reversed = "ABCDE".interspersed(with: "-").reversed()
57+
XCTAssertEqualSequences(reversed, "E-D-C-B-A")
58+
validateIndexTraversals(reversed)
59+
}
60+
}

0 commit comments

Comments
 (0)