Skip to content

Commit 718220d

Browse files
author
Tim Vermeulen
authored
Make uniqued() lazy by default (#71)
* Make `uniqued()` lazy by default
1 parent 551b4af commit 718220d

File tree

3 files changed

+117
-16
lines changed

3 files changed

+117
-16
lines changed

Guides/Unique.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
Methods to strip repeated elements from a sequence or collection.
77

8-
The `uniqued()` method returns an array, dropping duplicate elements
8+
The `uniqued()` method returns a sequence, dropping duplicate elements
99
from a sequence. The `uniqued(on:)` method does the same, using
1010
the result of the given closure to determine the "uniqueness" of each
1111
element.
@@ -14,29 +14,36 @@ element.
1414
let numbers = [1, 2, 3, 3, 2, 3, 3, 2, 2, 2, 1]
1515

1616
let unique = numbers.uniqued()
17-
// unique == [1, 2, 3]
17+
// Array(unique) == [1, 2, 3]
1818
```
1919

2020
## Detailed Design
2121

2222
Both methods are available for sequences, with the simplest limited to
2323
when the element type conforms to `Hashable`. Both methods preserve
24-
the relative order of the elements.
24+
the relative order of the elements. `uniqued(on:)` has a matching lazy
25+
version that is added to `LazySequenceProtocol`.
2526

2627
```swift
2728
extension Sequence where Element: Hashable {
28-
func uniqued() -> [Element]
29+
func uniqued() -> Uniqued<Self, Element>
2930
}
3031

3132
extension Sequence {
32-
func uniqued<T>(on: (Element) throws -> T) rethrows -> [Element]
33-
where T: Hashable
33+
func uniqued<Subject>(on projection: (Element) throws -> Subject) rethrows -> [Element]
34+
where Subject: Hashable
35+
}
36+
37+
extension LazySequenceProtocol {
38+
func uniqued<Subject>(on projection: @escaping (Element) -> Subject) -> Uniqued<Self, Subject>
39+
where Subject: Hashable
3440
}
3541
```
3642

3743
### Complexity
3844

39-
The `uniqued` methods are O(_n_) in both time and space complexity.
45+
The eager `uniqued(on:)` method is O(_n_) in both time and space complexity.
46+
The lazy versions are O(_1_).
4047

4148
### Comparison with other languages
4249

Sources/Algorithms/Unique.swift

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,82 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12+
/// A sequence wrapper that leaves out duplicate elements of a base sequence.
13+
public struct Uniqued<Base: Sequence, Subject: Hashable> {
14+
/// The base collection.
15+
@usableFromInline
16+
internal let base: Base
17+
18+
/// The projection function.
19+
@usableFromInline
20+
internal let projection: (Base.Element) -> Subject
21+
22+
@usableFromInline
23+
internal init(base: Base, projection: @escaping (Base.Element) -> Subject) {
24+
self.base = base
25+
self.projection = projection
26+
}
27+
}
28+
29+
extension Uniqued: Sequence {
30+
/// The iterator for a `Uniqued` sequence.
31+
public struct Iterator: IteratorProtocol {
32+
@usableFromInline
33+
internal var base: Base.Iterator
34+
35+
@usableFromInline
36+
internal let projection: (Base.Element) -> Subject
37+
38+
@usableFromInline
39+
internal var seen: Set<Subject> = []
40+
41+
@usableFromInline
42+
internal init(
43+
base: Base.Iterator,
44+
projection: @escaping (Base.Element) -> Subject
45+
) {
46+
self.base = base
47+
self.projection = projection
48+
}
49+
50+
@inlinable
51+
public mutating func next() -> Base.Element? {
52+
while let element = base.next() {
53+
if seen.insert(projection(element)).inserted {
54+
return element
55+
}
56+
}
57+
return nil
58+
}
59+
}
60+
61+
@inlinable
62+
public func makeIterator() -> Iterator {
63+
Iterator(base: base.makeIterator(), projection: projection)
64+
}
65+
}
66+
67+
extension Uniqued: LazySequenceProtocol where Base: LazySequenceProtocol {}
68+
1269
//===----------------------------------------------------------------------===//
1370
// uniqued()
1471
//===----------------------------------------------------------------------===//
1572

1673
extension Sequence where Element: Hashable {
17-
/// Returns an array with only the unique elements of this sequence, in the
74+
/// Returns a sequence with only the unique elements of this sequence, in the
1875
/// order of the first occurrence of each unique element.
1976
///
2077
/// let animals = ["dog", "pig", "cat", "ox", "dog", "cat"]
2178
/// let uniqued = animals.uniqued()
22-
/// print(uniqued)
79+
/// print(Array(uniqued))
2380
/// // Prints '["dog", "pig", "cat", "ox"]'
2481
///
25-
/// - Returns: An array with only the unique elements of this sequence.
82+
/// - Returns: A sequence with only the unique elements of this sequence.
2683
/// .
27-
/// - Complexity: O(*n*), where *n* is the length of the sequence.
84+
/// - Complexity: O(1).
2885
@inlinable
29-
public func uniqued() -> [Element] {
30-
uniqued(on: { $0 })
86+
public func uniqued() -> Uniqued<Self, Element> {
87+
Uniqued(base: self, projection: { $0 })
3188
}
3289
}
3390

@@ -40,7 +97,7 @@ extension Sequence {
4097
/// first characters:
4198
///
4299
/// let animals = ["dog", "pig", "cat", "ox", "cow", "owl"]
43-
/// let uniqued = animals.uniqued(on: {$0.first})
100+
/// let uniqued = animals.uniqued(on: { $0.first })
44101
/// print(uniqued)
45102
/// // Prints '["dog", "pig", "cat", "ox"]'
46103
///
@@ -67,3 +124,21 @@ extension Sequence {
67124
return result
68125
}
69126
}
127+
128+
//===----------------------------------------------------------------------===//
129+
// lazy.uniqued()
130+
//===----------------------------------------------------------------------===//
131+
132+
extension LazySequenceProtocol {
133+
/// Returns a lazy sequence with the unique elements of this sequence (as
134+
/// determined by the given projection), in the order of the first occurrence
135+
/// of each unique element.
136+
///
137+
/// - Complexity: O(1).
138+
@inlinable
139+
public func uniqued<Subject: Hashable>(
140+
on projection: @escaping (Element) -> Subject
141+
) -> Uniqued<Self, Subject> {
142+
Uniqued(base: self, projection: projection)
143+
}
144+
}

Tests/SwiftAlgorithmsTests/UniqueTests.swift

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ final class UniqueTests: XCTestCase {
1717
let a = repeatElement(1...10, count: 15).joined().shuffled()
1818
let b = a.uniqued()
1919
XCTAssertEqual(b.sorted(), Set(a).sorted())
20-
XCTAssertEqual(10, b.count)
20+
XCTAssertEqual(10, Array(b).count)
2121

2222
let c: [Int] = []
23-
XCTAssertEqual(c.uniqued(), [])
23+
XCTAssertEqualSequences(c.uniqued(), [])
24+
25+
let d = Array(repeating: 1, count: 10)
26+
XCTAssertEqualSequences(d.uniqued(), [1])
2427
}
2528

2629
func testUniqueOn() {
@@ -30,5 +33,21 @@ final class UniqueTests: XCTestCase {
3033

3134
let c: [Int] = []
3235
XCTAssertEqual(c.uniqued(on: { $0.bitWidth }), [])
36+
37+
let d = Array(repeating: "Andromeda", count: 10)
38+
XCTAssertEqualSequences(d.uniqued(on: { $0.first }), ["Andromeda"])
39+
}
40+
41+
func testLazyUniqueOn() {
42+
let a = ["Albemarle", "Abeforth", "Astrology", "Brandywine", "Beatrice", "Axiom"]
43+
let b = a.lazy.uniqued(on: { $0.first })
44+
XCTAssertEqualSequences(b, ["Albemarle", "Brandywine"])
45+
XCTAssertLazySequence(b)
46+
47+
let c: [Int] = []
48+
XCTAssertEqualSequences(c.lazy.uniqued(on: { $0.bitWidth }), [])
49+
50+
let d = Array(repeating: "Andromeda", count: 10)
51+
XCTAssertEqualSequences(d.lazy.uniqued(on: { $0.first }), ["Andromeda"])
3352
}
3453
}

0 commit comments

Comments
 (0)