Skip to content

Add RangeSet and discontiguous collection operations #28161

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

Merged
merged 26 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
91fa175
Add RangeSet and discontiguous collection operations
natecook1000 Nov 7, 2019
1cbe083
Fix broken invariant in single-range initializer
natecook1000 Nov 13, 2019
05be13e
Merge branch 'master' into nc_rangeset
natecook1000 Jan 16, 2020
f45e0ab
Revise proposed RangeSet & collections API
natecook1000 Jan 16, 2020
1ac6f07
Revise proposed RangeSet & collections API for proposal v3
natecook1000 Jan 21, 2020
63f878d
Merge branch 'master' into nc_rangeset
natecook1000 Feb 10, 2020
574c94c
[stdlib] Adopt the custom RangeSet storage type
natecook1000 Feb 10, 2020
b60223b
Update copyright notices
natecook1000 Feb 10, 2020
5321c34
Switch to standard library preconditions
natecook1000 Feb 10, 2020
4b43d8c
Merge branch 'master' into nc_rangeset
natecook1000 Feb 11, 2020
6c742f5
Remove ExpressibleByArrayLiteral conformance
natecook1000 Feb 11, 2020
d06d089
Add availability attributes to RangeSet &co
natecook1000 Feb 11, 2020
08f5773
Simplify RangeSetStorage's single-range case
natecook1000 Feb 14, 2020
c386444
Fill in remaining RangeSet documentation
natecook1000 Feb 14, 2020
62c3348
Minor revisions to RangeSet type docs
natecook1000 Feb 15, 2020
e09b1a8
Update test fixtures for RangeSet subscript
natecook1000 Feb 17, 2020
7c46522
Fix a documentation type
natecook1000 Feb 17, 2020
0723c4f
Remove RangeSet.contains(_: Range) method
natecook1000 Feb 17, 2020
a28cff9
Switch remaining preconditions to internal stdlib versions
natecook1000 Feb 17, 2020
bb0f357
Inversion of empty rangeset should be the whole bounds
natecook1000 Feb 18, 2020
348eeff
Inlinability annotations
natecook1000 Feb 21, 2020
41ca5b4
Revise inlining annotations after discussion w/ @milseman
natecook1000 Feb 21, 2020
64a32d9
Add missing internal annotation
natecook1000 Feb 22, 2020
d993755
Merge branch 'master' into nc_rangeset
natecook1000 Feb 22, 2020
6eb76ed
Update test expectations
natecook1000 Feb 22, 2020
8a5b441
Add availability to test fixtures
natecook1000 Feb 22, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions stdlib/private/StdlibCollectionUnittest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ add_swift_target_library(swiftStdlibCollectionUnittest ${SWIFT_STDLIB_LIBRARY_BU
CheckRangeReplaceableSliceType.swift
CheckSequenceInstance.swift
CheckSequenceType.swift
COWLoggingArray.swift
LoggingWrappers.swift
MinimalCollections.swift
RangeSelection.swift
Expand Down
123 changes: 123 additions & 0 deletions stdlib/private/StdlibCollectionUnittest/COWLoggingArray.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import StdlibUnittest

fileprivate var COWLoggingArray_CopyCount = 0

public func expectNoCopyOnWrite<T>(
_ elements: [T],
_ message: @autoclosure () -> String = "",
stackTrace: SourceLocStack = SourceLocStack(),
showFrame: Bool = true,
file: String = #file,
line: UInt = #line,
_ body: (inout COWLoggingArray<T>) -> Void
) {
let copyCountBeforeBody = COWLoggingArray_CopyCount
var loggingArray = COWLoggingArray(elements)
body(&loggingArray)
expectEqual(copyCountBeforeBody, COWLoggingArray_CopyCount, message(),
stackTrace: stackTrace.pushIf(showFrame, file: file, line: line),
showFrame: false)
}

public struct COWLoggingArray<Element> {
var storage: Storage

class Storage {
var buffer: UnsafeMutableBufferPointer<Element>
var count: Int
var capacity: Int {
buffer.count
}

init(capacity: Int) {
self.buffer = .allocate(capacity: capacity)
self.count = 0
}

deinit {
buffer.baseAddress!.deinitialize(count: count)
buffer.deallocate()
}

func cloned(capacity: Int? = nil) -> Storage {
let newCapacity = Swift.max(capacity ?? self.capacity, self.capacity)
let newStorage = Storage(capacity: newCapacity)
newStorage.buffer.baseAddress!
.initialize(from: buffer.baseAddress!, count: count)
newStorage.count = count
return newStorage
}
}

mutating func _makeUnique() {
if !isKnownUniquelyReferenced(&storage) {
storage = storage.cloned()
COWLoggingArray_CopyCount += 1
}
}
}

extension COWLoggingArray: RandomAccessCollection, RangeReplaceableCollection,
MutableCollection, ExpressibleByArrayLiteral
{
public var count: Int { storage.count }
public var startIndex: Int { 0 }
public var endIndex: Int { count }

public subscript(i: Int) -> Element {
get {
storage.buffer[i]
}
set {
_makeUnique()
storage.buffer[i] = newValue
}
}

public init() {
storage = Storage(capacity: 10)
}

public mutating func reserveCapacity(_ n: Int) {
if !isKnownUniquelyReferenced(&storage) {
COWLoggingArray_CopyCount += 1
storage = storage.cloned(capacity: n)
} else if count < n {
storage = storage.cloned(capacity: n)
}
}

public mutating func replaceSubrange<C>(_ subrange: Range<Int>, with newElements: C)
where C : Collection, Element == C.Element
{
_makeUnique()
let newCount = (count - subrange.count) + newElements.count
if newCount > storage.capacity {
storage = storage.cloned(capacity: newCount)
}

let startOfSubrange = storage.buffer.baseAddress! + subrange.lowerBound
let endOfSubrange = startOfSubrange + subrange.count
let endOfNewElements = startOfSubrange + newElements.count
let countAfterSubrange = count - subrange.upperBound

// clear out old elements
startOfSubrange.deinitialize(count: subrange.count)

// move elements above subrange
endOfNewElements.moveInitialize(from: endOfSubrange, count: countAfterSubrange)

// assign new elements
for (pointer, element) in zip(startOfSubrange..., newElements) {
pointer.initialize(to: element)
}

// update count
storage.count = newCount
}

public init(arrayLiteral elements: Element...) {
storage = Storage(capacity: elements.count)
replaceSubrange(0..<0, with: elements)
}
}
3 changes: 3 additions & 0 deletions stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,13 @@ set(SWIFTLIB_SOURCES
Availability.swift
CollectionDifference.swift
CollectionOfOne.swift
DiscontiguousSlice.swift
Diffing.swift
Mirror.swift
PlaygroundDisplay.swift
CommandLine.swift
RangeSet.swift
RangeSetStorage.swift
SliceBuffer.swift
SIMDVector.swift
UnfoldSequence.swift
Expand Down
141 changes: 140 additions & 1 deletion stdlib/public/core/CollectionAlgorithms.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors
// Copyright (c) 2014 - 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
Expand Down Expand Up @@ -209,6 +209,70 @@ extension BidirectionalCollection where Element: Equatable {
}
}

//===----------------------------------------------------------------------===//
// subranges(where:) / subranges(of:)
//===----------------------------------------------------------------------===//

extension Collection {
/// Returns the indices of all the elements that match the given predicate.
///
/// For example, you can use this method to find all the places that a
/// vowel occurs in a string.
///
/// let str = "Fresh cheese in a breeze"
/// let vowels: Set<Character> = ["a", "e", "i", "o", "u"]
/// let allTheVowels = str.subranges(where: { vowels.contains($0) })
/// // str[allTheVowels].count == 9
///
/// - Parameter predicate: A closure that takes an element as its argument
/// and returns a Boolean value that indicates whether the passed element
/// represents a match.
/// - Returns: A set of the indices of the elements for which `predicate`
/// returns `true`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
public func subranges(where predicate: (Element) throws -> Bool) rethrows
-> RangeSet<Index>
{
if isEmpty { return RangeSet() }

var result = RangeSet<Index>()
var i = startIndex
while i != endIndex {
let next = index(after: i)
if try predicate(self[i]) {
result._append(i..<next)
}
i = next
}

return result
}
}

extension Collection where Element: Equatable {
/// Returns the indices of all the elements that are equal to the given
/// element.
///
/// For example, you can use this method to find all the places that a
/// particular letter occurs in a string.
///
/// let str = "Fresh cheese in a breeze"
/// let allTheEs = str.subranges(of: "e")
/// // str[allTheEs].count == 7
///
/// - Parameter element: An element to look for in the collection.
/// - Returns: A set of the indices of the elements that are equal to
/// `element`.
///
/// - Complexity: O(*n*), where *n* is the length of the collection.
@available(macOS 9999, iOS 9999, tvOS 9999, watchOS 9999, *)
public func subranges(of element: Element) -> RangeSet<Index> {
subranges(where: { $0 == element })
}
}

//===----------------------------------------------------------------------===//
// partition(by:)
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -369,6 +433,81 @@ extension MutableCollection where Self: BidirectionalCollection {
}
}

//===----------------------------------------------------------------------===//
// _indexedStablePartition / _partitioningIndex
//===----------------------------------------------------------------------===//

extension MutableCollection {
/// Moves all elements at the indices satisfying `belongsInSecondPartition`
/// into a suffix of the collection, preserving their relative order, and
/// returns the start of the resulting suffix.
///
/// - Complexity: O(*n* log *n*) where *n* is the number of elements.
/// - Precondition:
/// `n == distance(from: range.lowerBound, to: range.upperBound)`
internal mutating func _indexedStablePartition(
count n: Int,
range: Range<Index>,
by belongsInSecondPartition: (Index) throws-> Bool
) rethrows -> Index {
if n == 0 { return range.lowerBound }
if n == 1 {
return try belongsInSecondPartition(range.lowerBound)
? range.lowerBound
: range.upperBound
}
let h = n / 2, i = index(range.lowerBound, offsetBy: h)
let j = try _indexedStablePartition(
count: h,
range: range.lowerBound..<i,
by: belongsInSecondPartition)
let k = try _indexedStablePartition(
count: n - h,
range: i..<range.upperBound,
by: belongsInSecondPartition)
return _rotate(in: j..<k, shiftingToStart: i)
}
}

//===----------------------------------------------------------------------===//
// _partitioningIndex(where:)
//===----------------------------------------------------------------------===//

extension Collection {
/// Returns the index of the first element in the collection that matches
/// the predicate.
///
/// The collection must already be partitioned according to the predicate.
/// That is, there should be an index `i` where for every element in
/// `collection[..<i]` the predicate is `false`, and for every element
/// in `collection[i...]` the predicate is `true`.
///
/// - Parameter predicate: A predicate that partitions the collection.
/// - Returns: The index of the first element in the collection for which
/// `predicate` returns `true`.
///
/// - Complexity: O(log *n*), where *n* is the length of this collection if
/// the collection conforms to `RandomAccessCollection`, otherwise O(*n*).
internal func _partitioningIndex(
where predicate: (Element) throws -> Bool
) rethrows -> Index {
var n = count
var l = startIndex

while n > 0 {
let half = n / 2
let mid = index(l, offsetBy: half)
if try predicate(self[mid]) {
n = half
} else {
l = index(after: mid)
n -= half + 1
}
}
return l
}
}

//===----------------------------------------------------------------------===//
// shuffled()/shuffle()
//===----------------------------------------------------------------------===//
Expand Down
Loading