Skip to content

Commit f674851

Browse files
committed
DocDocs
1 parent 4652ae7 commit f674851

File tree

3 files changed

+36
-259
lines changed

3 files changed

+36
-259
lines changed

Guides/PartialSort.md

Lines changed: 20 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,51 @@
1-
# Partial Sort
1+
# Partial Sort (sortedPrefix)
22

33
[[Source](https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/PartialSort.swift) |
44
[Tests](https://github.com/apple/swift-algorithms/blob/main/Tests/SwiftAlgorithmsTests/PartialSortTests.swift)]
55

6-
Returns a collection such that the `0...k` range contains the first k sorted elements of a sequence.
7-
The order of equal elements is not guaranteed to be preserved, and the order of the remaining elements is unspecified.
6+
Returns the first k elements of this collection when it's sorted.
87

9-
If you need to sort a sequence but only need access to a prefix of its elements,
10-
using this method can give you a performance boost over sorting the entire sequence.
8+
If you need to sort a sequence but only need access to a prefix of its
9+
elements, using this method can give you a performance boost over sorting
10+
the entire collection. The order of equal elements is guaranteed to be
11+
preserved.
1112

1213
```swift
1314
let numbers = [7,1,6,2,8,3,9]
14-
let almostSorted = numbers.partiallySorted(3, <)
15-
// [1, 2, 3, 9, 7, 6, 8]
15+
let smallestThree = numbers.sortedPrefix(<)
16+
// [1, 2, 3]
1617
```
1718

1819
## Detailed Design
1920

20-
This adds the in place `MutableCollection` method shown below:
21+
This adds the `Collection` method shown below:
2122

2223
```swift
23-
extension Sequence {
24-
func partiallySort(_ count: Int, by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows
24+
extension Collection {
25+
public func sortedPrefix(_ count: Int, by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]
2526
}
2627
```
2728

28-
Additionally, versions of this method that return a new array and abstractions for `Comparable` types are also provided:
29+
Additionally, a version of this method for `Comparable` types is also provided:
2930

3031
```swift
31-
extension MutableCollection where Self: RandomAccessCollection, Element: Comparable {
32-
public mutating func partiallySort(_ count: Int)
33-
}
34-
35-
extension Sequence {
36-
public func partiallySorted(_ count: Int, by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]
37-
}
38-
39-
extension Sequence where Element: Comparable {
40-
public func partiallySorted(_ count: Int) -> [Element]
32+
extension Collection where Element: Comparable {
33+
public func sortedPrefix(_ count: Int) -> [Element]
4134
}
4235
```
4336

4437
### Complexity
4538

46-
Partially sorting is a O(_k log n_) operation, where _k_ is the number of elements to sort
47-
and _n_ is the length of the sequence.
39+
The algorithm used is based on [Soroush Khanlou's research on this matter](https://khanlou.com/2018/12/analyzing-complexity/). The total complexity is `O(k log k + nk)`, which will result in a runtime close to `O(n)` if k is a small amount. If k is a large amount (more than 10% of the collection), we fallback to sorting the entire array. Realistically, this means the worst case is actually `O(n log n)`.
40+
41+
Here are some benchmarks we made that demonstrates how this implementation (SmallestM) behaves when k increases (before implementing the fallback):
4842

49-
`partiallySort(_:by:)` is a slight generalization of a priority queue. It's implemented
50-
as an in line heapsort that stops after _k_ runs.
43+
![Benchmark](https://i.imgur.com/F5UEQnl.png)
44+
![Benchmark 2](https://i.imgur.com/Bm9DKRc.png)
5145

5246
### Comparison with other languages
5347

54-
**C++:** The `<algorithm>` library defines a `partial_sort` function with similar
55-
semantics to this one.
48+
**C++:** The `<algorithm>` library defines a `partial_sort` function where the entire array is returned.
5649

5750
**Python:** Defines a `heapq` priority queue that can be used to manually
5851
achieve the same result.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Read more about the package, and the intent behind it, in the [announcement on s
3030

3131
#### Partial sorting
3232

33-
- [`partiallySorted(_:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/PartialSort.md): Sorts a sequence only up to a specific index, leaving the remaining elements unsorted.
33+
- [`sortedPrefix(_:by:)`](https://github.com/apple/swift-algorithms/blob/main/Guides/PartialSort.md): Returns the first k elements of a sorted collection.
3434

3535
#### Other useful operations
3636

Sources/Algorithms/PartialSort.swift

Lines changed: 15 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -10,32 +10,27 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
extension Sequence {
13-
/// Returns the elements of the sequence such that the 0...k range contains
14-
/// the first k sorted elements in this sequence, using the given predicate
15-
/// as the comparison between elements.
13+
/// Returns the first k elements of this collection when it's sorted using
14+
/// the given predicate as the comparison between elements.
1615
///
1716
/// This example partially sorts an array of integers to retrieve its three
1817
/// smallest values:
1918
///
2019
/// let numbers = [7,1,6,2,8,3,9]
21-
/// let almostSorted = numbers.partiallySorted(3, <)
22-
/// // [1, 2, 3, 9, 7, 6, 8]
23-
/// let smallestThree = almostSorted.prefix(3)
20+
/// let smallestThree = numbers.sortedPrefix(3, <)
2421
/// // [1, 2, 3]
2522
///
26-
/// The order of equal elements is not guaranteed to be preserved, and the
27-
/// order of the remaining elements is unspecified.
28-
///
29-
/// If you need to sort a sequence but only need access to a prefix of its
23+
/// If you need to sort a collection but only need access to a prefix of its
3024
/// elements, using this method can give you a performance boost over sorting
31-
/// the entire sequence.
25+
/// the entire collection. The order of equal elements is guaranteed to be
26+
/// preserved.
3227
///
3328
/// - Parameter count: The k number of elements to partially sort.
3429
/// - Parameter areInIncreasingOrder: A predicate that returns true if its
3530
/// first argument should be ordered before its second argument;
36-
/// otherwise, false.
31+
/// otherwise, false.
3732
///
38-
/// - Complexity: O(k log n)
33+
/// - Complexity: O(k log k + nk)
3934
public func partiallySorted(
4035
_ count: Int,
4136
by areInIncreasingOrder: (Element, Element) throws -> Bool
@@ -47,236 +42,25 @@ extension Sequence {
4742
}
4843

4944
extension Sequence where Element: Comparable {
50-
/// Returns the elements of the sequence such that the 0...k range contains
51-
/// the first k smallest elements in this sequence.
45+
/// Returns the first k elements of this collection when it's sorted.
5246
///
5347
/// This example partially sorts an array of integers to retrieve its three
5448
/// smallest values:
5549
///
5650
/// let numbers = [7,1,6,2,8,3,9]
57-
/// let almostSorted = numbers.partiallySorted(3)
58-
/// // [1, 2, 3, 9, 7, 6, 8]
59-
/// let smallestThree = almostSorted.prefix(3)
51+
/// let smallestThree = numbers.sortedPrefix(<)
6052
/// // [1, 2, 3]
6153
///
62-
/// The order of equal elements is not guaranteed to be preserved, and the
63-
/// order of the remaining elements is unspecified.
64-
///
65-
/// If you need to sort a sequence but only need access to a prefix of
66-
/// its elements, using this method can give you a performance boost over
67-
/// sorting the entire sequence.
54+
/// If you need to sort a sequence but only need access to a prefix of its
55+
/// elements, using this method can give you a performance boost over sorting
56+
/// the entire collection. The order of equal elements is guaranteed to be
57+
/// preserved.
6858
///
6959
/// - Parameter count: The k number of elements to partially sort
7060
/// in ascending order.
7161
///
72-
/// - Complexity: O(k log n)
62+
/// - Complexity: O(k log k + nk)
7363
public func partiallySorted(_ count: Int) -> [Element] {
7464
return partiallySorted(count, by: <)
7565
}
7666
}
77-
78-
extension MutableCollection where Self: RandomAccessCollection {
79-
/// Rearranges this collection such that the 0...k range contains the first
80-
/// k sorted elements in this collection, using the given predicate as the
81-
/// comparison between elements.
82-
///
83-
/// This example partially sorts an array of integers to retrieve its three
84-
/// smallest values:
85-
///
86-
/// var numbers = [7,1,6,2,8,3,9]
87-
/// numbers.partiallySort(3, <)
88-
/// // [1, 2, 3, 9, 7, 6, 8]
89-
/// let smallestThree = numbers.prefix(3)
90-
/// // [1, 2, 3]
91-
///
92-
/// The order of equal elements is not guaranteed to be preserved, and the
93-
/// order of the remaining elements is unspecified.
94-
///
95-
/// If you need to sort a collection but only need access to a prefix of its
96-
/// elements, using this method can give you a performance boost over sorting
97-
/// the entire collection.
98-
///
99-
/// - Parameter count: The k number of elements to partially sort.
100-
/// - Parameter areInIncreasingOrder: A predicate that returns true if its
101-
/// first argument should be ordered before its second argument;
102-
/// otherwise, false.
103-
///
104-
/// - Complexity: O(k log n)
105-
public mutating func partiallySort(
106-
_ count: Int,
107-
by areInIncreasingOrder: (Element, Element) throws -> Bool
108-
) rethrows {
109-
try __partiallySort(count, by: areInIncreasingOrder)
110-
}
111-
}
112-
113-
extension MutableCollection
114-
where Self: RandomAccessCollection, Element: Comparable {
115-
/// Rearranges this collection such that the 0...k range contains the first
116-
/// k smallest elements in this collection.
117-
///
118-
/// This example partially sorts an array of integers to retrieve its three
119-
/// smallest values:
120-
///
121-
/// var numbers = [7,1,6,2,8,3,9]
122-
/// numbers.partiallySort(3)
123-
/// // [1, 2, 3, 9, 7, 6, 8]
124-
/// let smallestThree = numbers.prefix(3)
125-
/// // [1, 2, 3]
126-
///
127-
/// The order of equal elements is not guaranteed to be preserved, and the
128-
/// order of the remaining elements is unspecified.
129-
///
130-
/// If you need to sort a collection but only need access to a prefix of its
131-
/// elements, using this method can give you a performance boost over sorting
132-
/// the entire collection.
133-
///
134-
/// - Parameter count: The k number of elements to partially sort
135-
/// in ascending order.
136-
///
137-
/// - Complexity: O(k log n)
138-
public mutating func partiallySort(_ count: Int) {
139-
partiallySort(count, by: <)
140-
}
141-
}
142-
143-
//===----------------------------------------------------------------------===//
144-
// __partiallySort(_:by:)
145-
//===----------------------------------------------------------------------===//
146-
147-
extension MutableCollection where Self: RandomAccessCollection {
148-
typealias Priority = (Element, Element) throws -> Bool
149-
150-
/// Partially sorts this collection by using an in place heapsort that stops
151-
/// after we find the desired k amount
152-
/// of elements. The heap is stored and processed in reverse order so that
153-
/// the collection doesn't have to be flipped once the final result is found.
154-
///
155-
/// Complexity: O(k log n)
156-
mutating func __partiallySort(
157-
_ k: Int,
158-
by areInIncreasingOrder: Priority
159-
) rethrows {
160-
assert(k >= 0, """
161-
Cannot partially sort with a negative amount of elements!
162-
"""
163-
)
164-
165-
assert(k <= count, """
166-
Cannot partially sort more than this Sequence's size!
167-
"""
168-
)
169-
170-
guard k > 0 else {
171-
return
172-
}
173-
guard isEmpty == false else {
174-
return
175-
}
176-
var heapEndIndex = 0
177-
for i in (count / 2)..<count {
178-
try siftDown(i, by: areInIncreasingOrder, heapEndIndex: heapEndIndex)
179-
}
180-
var iterator = (0..<k).makeIterator()
181-
_ = iterator.next()
182-
swapAt(index(before: endIndex), index(startIndex, offsetBy: heapEndIndex))
183-
heapEndIndex += 1
184-
while let _ = iterator.next() {
185-
try siftDown(
186-
count - 1,
187-
by: areInIncreasingOrder,
188-
heapEndIndex: heapEndIndex
189-
)
190-
swapAt(index(before: endIndex), index(startIndex, offsetBy: heapEndIndex))
191-
heapEndIndex += 1
192-
}
193-
}
194-
195-
/// Sifts down an element from this heap.
196-
/// The heap is stored in reverse order, so sifting down will actually
197-
/// move the element up in the heap.
198-
///
199-
/// - Parameter i: The element index to sift down
200-
/// - Parameter by: The predicate to use when determining the priority
201-
/// of elements in the heap
202-
/// - Parameter heapEndIndex: The index in reverse order, where the heap ends.
203-
private mutating func siftDown(
204-
_ i: Int,
205-
by priority: Priority,
206-
heapEndIndex: Int
207-
) rethrows {
208-
let indexToSwap = try highestPriorityIndex(
209-
of: i,
210-
by: priority,
211-
heapEndIndex: heapEndIndex
212-
)
213-
guard indexToSwap != i else {
214-
return
215-
}
216-
swapAt(
217-
index(startIndex, offsetBy: i),
218-
index(startIndex, offsetBy: indexToSwap)
219-
)
220-
try siftDown(indexToSwap, by: priority, heapEndIndex: heapEndIndex)
221-
}
222-
223-
private func highestPriorityIndex(
224-
of index: Int,
225-
by priority: Priority,
226-
heapEndIndex: Int
227-
) rethrows -> Int {
228-
let reverseHeapTrueIndex = self.count - 1 - index
229-
let leftChildDistance =
230-
leftChildIndex(of: reverseHeapTrueIndex) - reverseHeapTrueIndex
231-
let leftChild = index - leftChildDistance
232-
233-
let rightChildDistance =
234-
rightChildIndex(of: reverseHeapTrueIndex) - reverseHeapTrueIndex
235-
let rightChild = index - rightChildDistance
236-
237-
let left = try highestPriorityIndex(
238-
of: index,
239-
and: leftChild,
240-
by: priority,
241-
heapEndIndex: heapEndIndex
242-
)
243-
244-
let right = try highestPriorityIndex(
245-
of: index,
246-
and: rightChild,
247-
by: priority,
248-
heapEndIndex: heapEndIndex
249-
)
250-
return try highestPriorityIndex(
251-
of: left,
252-
and: right,
253-
by: priority,
254-
heapEndIndex: heapEndIndex
255-
)
256-
}
257-
258-
private func leftChildIndex(of index: Int) -> Int {
259-
return (2 * index) + 1
260-
}
261-
262-
private func rightChildIndex(of index: Int) -> Int {
263-
return (2 * index) + 2
264-
}
265-
266-
private func highestPriorityIndex(
267-
of parent: Int,
268-
and child: Int,
269-
by priority: Priority,
270-
heapEndIndex: Int
271-
) rethrows -> Int {
272-
guard child >= heapEndIndex else {
273-
return parent
274-
}
275-
let childElement = self[index(startIndex, offsetBy: child)]
276-
let parentElement = self[index(startIndex, offsetBy: parent)]
277-
guard try priority(childElement, parentElement) else {
278-
return parent
279-
}
280-
return child
281-
}
282-
}

0 commit comments

Comments
 (0)