Skip to content

Commit d7ee560

Browse files
committed
[stdlib] Implement partition API change (SE-0120)
1 parent 0358235 commit d7ee560

File tree

3 files changed

+115
-129
lines changed

3 files changed

+115
-129
lines changed

docs/proposals/InoutCOWOptimization.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ could be written as follows:
5555
let (start, end) = (startIndex, endIndex)
5656
if start != end && start.succ() != end {
5757
let pivot = self[start]
58-
let mid = partition({compare($0, pivot)})
58+
let mid = partition(by: {!compare($0, pivot)})
5959
**self[start...mid].quickSort(compare)**
6060
**self[mid...end].quickSort(compare)**
6161
}

stdlib/public/core/CollectionAlgorithms.swift.gyb

Lines changed: 74 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -142,149 +142,84 @@ orderingExplanation = """\
142142
// partition()
143143
//===----------------------------------------------------------------------===//
144144

145-
% # Generate two versions: with explicit predicates and with
146-
% # a Comparable requirement.
147-
% for preds in [True, False]:
148-
149-
% if preds:
150-
151-
extension MutableCollection where Self : RandomAccessCollection
152-
153-
% else:
154-
155-
extension MutableCollection
156-
where Self : RandomAccessCollection, ${IElement} : Comparable
157-
158-
% end
159-
{
160-
161-
% if preds:
162-
/// Reorders the elements in the collection and returns a pivot index, using
163-
/// the given predicate as the comparison between elements.
164-
///
165-
/// This method is typically one step of a sorting algorithm. A collection is
166-
/// partitioned around a pivot index when each of the elements before the
167-
/// pivot is correctly ordered before each of the elements at or after the
168-
/// pivot. The `partition(by:)` method reorders the elements of
169-
/// the collection and returns a pivot index that satisfies this condition,
170-
/// using the given predicate to determine the relative order of any two
171-
/// elements.
172-
///
173-
${orderingExplanation}
174-
/// Here's an example that uses a predicate that orders elements from largest
175-
/// to smallest:
176-
///
177-
/// var numbers = [50, 30, 60, 50, 80, 10, 40, 30]
178-
/// let pivot = numbers.partition { a, b in a > b }
179-
///
180-
/// print(pivot)
181-
/// // Prints "2"
182-
/// print(numbers)
183-
/// // Prints "[60, 80, 50, 50, 30, 10, 40, 30]"
184-
///
185-
/// The return value of the call to `numbers.partition()` is the pivot for
186-
/// the rearranged `numbers` array. `pivot` divides the collection into two
187-
/// subranges, `numbers[0..<pivot]` and `numbers[pivot..<8]`.
188-
///
189-
/// print(numbers[0..<pivot])
190-
/// // Prints "[60, 80]"
191-
/// print(numbers[pivot..<8])
192-
/// // Prints "[50, 50, 30, 10, 40, 30]"
193-
///
194-
/// The elements of `numbers` are rearranged so that every element in the
195-
/// subrange before `pivot` is ordered before every element in the subrange
196-
/// after. Because the supplied predicate returns `true` when its first
197-
/// argument is greater than its second argument, larger elements are
198-
/// ordered before smaller elements.
199-
///
200-
/// - Parameter areInIncreasingOrder: A predicate that returns `true` if its first
201-
/// argument should be ordered before its second argument; otherwise,
202-
/// `false`.
203-
/// - Returns: A pivot index, such that every element before the pivot is
204-
/// ordered before every element at or above the pivot, using
205-
/// `areInIncreasingOrder` to determine the relative order of any two elements.
206-
/// The returned pivot is equal to the collection's end index only if the
207-
/// collection is empty.
208-
///
209-
/// - SeeAlso: `partition()`
145+
extension MutableCollection {
210146
public mutating func partition(
211-
by areInIncreasingOrder: @noescape (${IElement}, ${IElement}) -> Bool
212-
) -> Index
147+
by belongsInSecondPartition: @noescape (${IElement}) throws -> Bool
148+
) rethrows -> Index {
213149

214-
% else:
150+
var pivot = startIndex
151+
while true {
152+
if pivot == endIndex {
153+
return pivot
154+
}
155+
if try belongsInSecondPartition(self[pivot]) {
156+
break
157+
}
158+
formIndex(after: &pivot)
159+
}
215160

216-
/// Reorders the elements in the collection and returns a pivot index.
217-
///
218-
/// This method is typically one step of a sorting algorithm. A collection is
219-
/// partitioned around a pivot index when each of the elements before the
220-
/// pivot are less than each of the elements at or after the pivot. The
221-
/// `partition()` method reorders the elements of the collection and returns
222-
/// a pivot index that satisfies this condition.
223-
///
224-
/// For example:
225-
///
226-
/// var numbers = [50, 30, 60, 50, 80, 10, 40, 30]
227-
/// let pivot = numbers.partition()
228-
///
229-
/// print(pivot)
230-
/// // Prints "4"
231-
/// print(numbers)
232-
/// // Prints "[10, 30, 30, 40, 50, 80, 50, 60]"
233-
///
234-
/// The return value of the call to `numbers.partition()` is the pivot for
235-
/// the rearranged `numbers` array. `pivot` divides the collection into two
236-
/// subranges, `numbers[0..<pivot]` and `numbers[pivot..<8]`.
237-
///
238-
/// print(numbers[0..<pivot])
239-
/// // Prints "[10, 30, 30, 40]"
240-
/// print(numbers[pivot..<8])
241-
/// // Prints "[50, 80, 50, 60]"
242-
///
243-
/// The elements of `numbers` are rearranged so that every element in the
244-
/// subrange before `pivot` is less than every element in the subrange
245-
/// after.
246-
///
247-
/// - Returns: A pivot index, such that every element before the pivot is
248-
/// less than every element at or above the pivot. The returned pivot is
249-
/// equal to the collection's end index only if the collection is empty.
250-
///
251-
/// - SeeAlso: `partition(by:)`
252-
public mutating func partition() -> Index
161+
var i = index(after: pivot)
162+
while i < endIndex {
163+
if try !belongsInSecondPartition(self[i]) {
164+
swap(&self[i], &self[pivot])
165+
formIndex(after: &pivot)
166+
}
167+
formIndex(after: &i)
168+
}
169+
return pivot
170+
}
171+
}
253172

254-
% end
255-
{
256-
let maybeOffset = _withUnsafeMutableBufferPointerIfSupported {
173+
extension MutableCollection where Self: BidirectionalCollection {
174+
public mutating func partition(
175+
by belongsInSecondPartition: @noescape (${IElement}) throws -> Bool
176+
) rethrows -> Index {
177+
let maybeOffset = try _withUnsafeMutableBufferPointerIfSupported {
257178
(baseAddress, count) -> Int in
258179
var bufferPointer =
259180
UnsafeMutableBufferPointer(start: baseAddress, count: count)
260-
let unsafeBufferPivot = bufferPointer.partition(
261-
% if preds:
262-
by: areInIncreasingOrder
263-
% end
264-
)
181+
let unsafeBufferPivot = try bufferPointer.partition(
182+
by: belongsInSecondPartition)
265183
return unsafeBufferPivot - bufferPointer.startIndex
266184
}
267185
if let offset = maybeOffset {
268186
return index(startIndex, offsetBy: numericCast(offset))
269187
}
270188

271-
% if preds:
272-
typealias EscapingBinaryPredicate =
273-
(${IElement}, ${IElement}) -> Bool
274-
var escapableIsOrderedBefore =
275-
unsafeBitCast(areInIncreasingOrder, to: EscapingBinaryPredicate.self)
276-
return _partition(
277-
&self,
278-
subRange: startIndex..<endIndex,
279-
by: &escapableIsOrderedBefore)
280-
% else:
281-
return _partition(&self, subRange: startIndex..<endIndex)
282-
% end
189+
var lo = startIndex
190+
var hi = endIndex
191+
192+
// 'Loop' invariants (at start of Loop, all are true):
193+
// * lo < hi
194+
// * predicate(self[i]) == false, for i in startIndex ..< lo
195+
// * predicate(self[i]) == true, for i in hi ..< endIndex
196+
197+
Loop: while true {
198+
FindLo: repeat {
199+
while lo < hi {
200+
if try belongsInSecondPartition(self[lo]) { break FindLo }
201+
formIndex(after: &lo)
202+
}
203+
break Loop
204+
} while false
205+
206+
FindHi: repeat {
207+
formIndex(before: &hi)
208+
while lo < hi {
209+
if try !belongsInSecondPartition(self[hi]) { break FindHi }
210+
formIndex(before: &hi)
211+
}
212+
break Loop
213+
} while false
214+
215+
swap(&self[lo], &self[hi])
216+
formIndex(after: &lo)
217+
}
218+
219+
return lo
283220
}
284221
}
285222

286-
% end
287-
288223
//===----------------------------------------------------------------------===//
289224
// sorted()
290225
//===----------------------------------------------------------------------===//
@@ -632,6 +567,12 @@ ${subscriptCommentPost}
632567
//===--- Unavailable stuff ------------------------------------------------===//
633568

634569
extension MutableCollection where Self : RandomAccessCollection {
570+
@available(*, unavailable, message: "call partition(by:)")
571+
public mutating func partition(
572+
isOrderedBefore: @noescape (${IElement}, ${IElement}) -> Bool
573+
) -> Index {
574+
Builtin.unreachable()
575+
}
635576

636577
@available(*, unavailable, message: "slice the collection using the range, and call partition(by:)")
637578
public mutating func partition(
@@ -645,7 +586,12 @@ extension MutableCollection where Self : RandomAccessCollection {
645586
extension MutableCollection
646587
where Self : RandomAccessCollection, ${IElement} : Comparable {
647588

648-
@available(*, unavailable, message: "slice the collection using the range, and call partition()")
589+
@available(*, unavailable, message: "call partition(by:)")
590+
public mutating func partition() -> Index {
591+
Builtin.unreachable()
592+
}
593+
594+
@available(*, unavailable, message: "slice the collection using the range, and call partition(by:)")
649595
public mutating func partition(_ range: Range<Index>) -> Index {
650596
Builtin.unreachable()
651597
}

stdlib/public/core/MutableCollection.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,46 @@ public protocol MutableCollection : MutableIndexable, Collection {
267267
/// the range must be valid indices of the collection.
268268
subscript(bounds: Range<Index>) -> SubSequence {get set}
269269

270+
/// Reorders the elements of the collection such that all the elements
271+
/// that match the given predicate are after all the elements that do
272+
/// not match the predicate.
273+
///
274+
/// After partitioning a collection, there is a pivot index `p` where
275+
/// no element before `p` satisfies the `belongsInSecondPartition`
276+
/// predicate and every element at or after `p` satisfies
277+
/// `belongsInSecondPartition`.
278+
///
279+
/// In the following example, an array of numbers is partitioned by a
280+
/// predicate that matches elements greater than 30.
281+
///
282+
/// var numbers = [30, 40, 20, 30, 30, 60, 10]
283+
/// let p = numbers.partition(by: { $0 > 30 })
284+
/// // p == 5
285+
/// // numbers == [30, 10, 20, 30, 30, 60, 40]
286+
///
287+
/// The `numbers` array is now arranged in two partitions. The first
288+
/// partition, `numbers.prefix(upTo: p)`, is made up of the elements that
289+
/// are not greater than 30. The second partition, `numbers.suffix(from: p)`,
290+
/// is made up of the elements that *are* greater than 30.
291+
///
292+
/// let first = numbers.prefix(upTo: p)
293+
/// // first == [30, 10, 20, 30, 30]
294+
/// let second = numbers.suffix(from: p)
295+
/// // second == [60, 40]
296+
///
297+
/// - Parameter belongsInSecondPartition: A predicate used to partition
298+
/// the collection. All elements satisfying this predicate are ordered
299+
/// after all elements not satisfying it.
300+
/// - Returns: The index of the first element in the reordered collection
301+
/// that matches `belongsInSecondPartition`. If no elements in the
302+
/// collection match `belongsInSecondPartition`, the returned index is
303+
/// equal to the collection's `endIndex`.
304+
///
305+
/// - Complexity: O(n)
306+
mutating func partition(
307+
by belongsInSecondPartition: @noescape (Iterator.Element) throws -> Bool
308+
) rethrows -> Index
309+
270310
/// Call `body(p)`, where `p` is a pointer to the collection's
271311
/// mutable contiguous storage. If no such storage exists, it is
272312
/// first created. If the collection does not support an internal

0 commit comments

Comments
 (0)