//===--- DiscontiguousSlice.swift -----------------------------*- swift -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020 - 2023 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 // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// /// A collection wrapper that provides access to the elements of a collection, /// indexed by a set of indices. @available(SwiftStdlib 6.0, *) public struct DiscontiguousSlice { internal var _base: Base /// The set of subranges that are available through this discontiguous slice. public let subranges: RangeSet /// The collection that the indexed collection wraps. public var base: Base { return _base } @usableFromInline internal init(_base: Base, subranges: RangeSet) { self._base = _base self.subranges = subranges } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: Sendable where Base: Sendable, Base.Index: Sendable {} @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: Equatable where Base.Element: Equatable { public static func ==(lhs: Self, rhs: Self) -> Bool { lhs.elementsEqual(rhs) } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: Hashable where Base.Element: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(count) // delimiter; do not remove for element in self { hasher.combine(element) } } } @available(SwiftStdlib 6.0, *) @_unavailableInEmbedded extension DiscontiguousSlice: CustomStringConvertible { public var description: String { _makeCollectionDescription() } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice { /// A position in a `DiscontiguousSlice`. public struct Index { /// The index of the range that contains `base`. internal let _rangeOffset: Int /// The position of this index in the base collection. public let base: Base.Index internal init(_rangeOffset: Int, base: Base.Index) { self._rangeOffset = _rangeOffset self.base = base } } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice.Index: Equatable { public static func == (left: Self, right: Self) -> Bool { left.base == right.base } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice.Index: Hashable where Base.Index: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(base) } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice.Index: Comparable { public static func < (left: Self, right: Self) -> Bool { left.base < right.base } } @available(SwiftStdlib 6.0, *) @_unavailableInEmbedded extension DiscontiguousSlice.Index: CustomStringConvertible { public var description: String { "" } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice.Index: Sendable where Base.Index: Sendable {} @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: Sequence { public typealias Element = Base.Element public typealias Iterator = IndexingIterator public func _customContainsEquatableElement(_ element: Element) -> Bool? { _customIndexOfEquatableElement(element).map { $0 != nil } } public __consuming func _copyToContiguousArray() -> ContiguousArray { var result: ContiguousArray = [] for range in subranges.ranges { result.append(contentsOf: base[range]) } return result } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: Collection { public typealias SubSequence = Self public typealias Indices = DefaultIndices public var startIndex: Index { subranges.isEmpty ? endIndex : Index(_rangeOffset: 0, base: subranges.ranges[0].lowerBound) } public var endIndex: Index { Index(_rangeOffset: subranges.ranges.endIndex, base: _base.endIndex) } public var count: Int { var c = 0 for range in subranges.ranges { c += base[range].count } return c } public var isEmpty: Bool { subranges.isEmpty } public func distance(from start: Index, to end: Index) -> Int { let ranges = subranges.ranges[start._rangeOffset ... end._rangeOffset] guard ranges.count > 1 else { return _base[ranges[0]].distance(from: start.base, to: end.base) } let firstRange = ranges.first! let lastRange = ranges.last! let head = _base[firstRange].distance( from: start.base, to: firstRange.upperBound) let tail = _base[lastRange].distance( from: lastRange.lowerBound, to: end.base) let middle = ranges[1 ..< (ranges.endIndex - 1)].reduce(0) { $0 + _base[$1].count } return head + middle + tail } public func index(after i: Index) -> Index { // Note: index validation performed by the underlying collections only let currentRange = subranges.ranges[i._rangeOffset] let nextIndex = _base[currentRange].index(after: i.base) if nextIndex < currentRange.upperBound { return Index(_rangeOffset: i._rangeOffset, base: nextIndex) } let nextOffset = i._rangeOffset + 1 guard nextOffset < subranges.ranges.endIndex else { return endIndex } return Index( _rangeOffset: nextOffset, base: subranges.ranges[nextOffset].lowerBound) } public subscript(i: Index) -> Base.Element { // Note: index validation performed by the base collection only _base[subranges.ranges[i._rangeOffset]][i.base] } public subscript(bounds: Range) -> DiscontiguousSlice { let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base let baseSlice = base[baseBounds] let baseAdjustedBounds = baseSlice.startIndex ..< baseSlice.endIndex let subset = subranges.intersection(RangeSet(baseAdjustedBounds)) return DiscontiguousSlice(_base: base, subranges: subset) } @usableFromInline internal func _index(of baseIndex: Base.Index) -> Index? { let rangeOffset = subranges.ranges ._partitioningIndex { $0.upperBound >= baseIndex } let subrange = subranges.ranges[rangeOffset] guard subrange.contains(baseIndex) else { return nil } return Index(_rangeOffset: rangeOffset, base: baseIndex) } public func _customIndexOfEquatableElement(_ element: Element) -> Index?? { var definite = true for (i, range) in subranges.ranges.enumerated() { guard let baseResult = _base[range] ._customIndexOfEquatableElement(element) else { definite = false continue } guard let baseIndex = baseResult else { continue } return Index(_rangeOffset: i, base: baseIndex) } if definite { return .some(nil) } else { return nil } } public func _customLastIndexOfEquatableElement( _ element: Element ) -> Index?? { var definite = true for (i, range) in subranges.ranges.enumerated().reversed() { guard let baseResult = _base[range] ._customLastIndexOfEquatableElement(element) else { definite = false continue } guard let baseIndex = baseResult else { continue } return Index(_rangeOffset: i, base: baseIndex) } if definite { return .some(nil) } else { return nil } } public func _failEarlyRangeCheck( _ index: Index, bounds: Range ) { let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base let offsetBounds = bounds.lowerBound._rangeOffset ..< bounds.upperBound._rangeOffset _base._failEarlyRangeCheck(index.base, bounds: baseBounds) _precondition(offsetBounds.contains(index._rangeOffset)) if index._rangeOffset == endIndex._rangeOffset { _precondition(index.base == _base.endIndex) } else { _precondition(subranges.ranges[index._rangeOffset].contains(index.base)) } } public func _failEarlyRangeCheck(_ index: Index, bounds: ClosedRange) { let baseBounds = bounds.lowerBound.base ... bounds.upperBound.base let offsetBounds = bounds.lowerBound._rangeOffset ... bounds.upperBound._rangeOffset _base._failEarlyRangeCheck(index.base, bounds: baseBounds) _precondition(offsetBounds.contains(index._rangeOffset)) if index._rangeOffset == endIndex._rangeOffset { _precondition(index.base == _base.endIndex) } else { _precondition(subranges.ranges[index._rangeOffset].contains(index.base)) } } public func _failEarlyRangeCheck(_ range: Range, bounds: Range) { let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base let baseRange = range.lowerBound.base ..< range.upperBound.base let offsetBounds = bounds.lowerBound._rangeOffset ..< bounds.upperBound._rangeOffset let offsetRange = range.lowerBound._rangeOffset ..< range.upperBound._rangeOffset _base._failEarlyRangeCheck(baseRange, bounds: baseBounds) _precondition(offsetBounds.contains(offsetRange.lowerBound)) _precondition(offsetRange.upperBound <= offsetBounds.upperBound) if offsetRange.lowerBound == endIndex._rangeOffset { _precondition(baseRange.lowerBound == _base.endIndex) } else { _precondition( subranges.ranges[offsetRange.lowerBound].contains(baseRange.lowerBound)) } if offsetRange.upperBound == endIndex._rangeOffset { _precondition(baseRange.upperBound == _base.endIndex) } else { _precondition( subranges.ranges[offsetRange.upperBound].contains(baseRange.upperBound)) } } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice: BidirectionalCollection where Base: BidirectionalCollection { public func index(before i: Index) -> Index { _precondition(i > startIndex, "Can't move index before startIndex") if i.base == base.endIndex || i.base == subranges.ranges[i._rangeOffset].lowerBound { // Go to next subrange let nextOffset = i._rangeOffset - 1 let nextRange = subranges.ranges[nextOffset] let nextBase = base[nextRange].index(before: nextRange.upperBound) return Index(_rangeOffset: nextOffset, base: nextBase) } else { // Move within current subrange let currentRange = subranges.ranges[i._rangeOffset] let nextBase = base[currentRange].index(before: i.base) return Index(_rangeOffset: i._rangeOffset, base: nextBase) } } } @available(SwiftStdlib 6.0, *) extension DiscontiguousSlice where Base: MutableCollection { /// Accesses the element at the specified position. /// /// For example, you can replace an element of an array by using its /// subscript. /// /// var streets = ["Adams", "Bryant", "Channing", "Douglas", "Evarts"] /// streets[1] = "Butler" /// print(streets[1]) /// // Prints "Butler" /// /// You can subscript a collection with any valid index other than the /// collection's end index. The end index refers to the position one /// past the last element of a collection, so it doesn't correspond with an /// element. /// /// - Parameter i: The position of the element to access. `i` /// must be a valid index of the collection that is not equal to the /// `endIndex` property. /// /// - Complexity: O(1) public subscript(i: Index) -> Base.Element { get { _base[subranges.ranges[i._rangeOffset]][i.base] } set { _base[subranges.ranges[i._rangeOffset]][i.base] = newValue } } } // MARK: Accessing DiscontiguousSlices extension Collection { /// Accesses a view of this collection with the elements at the given /// indices. /// /// - Parameter subranges: The indices of the elements to retrieve from this /// collection. /// - Returns: A collection of the elements at the positions in `subranges`. /// /// - Complexity: O(1) @available(SwiftStdlib 6.0, *) public subscript(subranges: RangeSet) -> DiscontiguousSlice { DiscontiguousSlice(_base: self, subranges: subranges) } } extension Collection { /// Returns a collection of the elements in this collection that are not /// represented by the given range set. /// /// For example, this code sample finds the indices of all the vowel /// characters in the string, and then retrieves a collection that omits /// those characters. /// /// let str = "The rain in Spain stays mainly in the plain." /// let vowels: Set = ["a", "e", "i", "o", "u"] /// let vowelIndices = str.indices(where: { vowels.contains($0) }) /// /// let disemvoweled = str.removingSubranges(vowelIndices) /// print(String(disemvoweled)) /// // Prints "Th rn n Spn stys mnly n th pln." /// /// - Parameter subranges: A range set representing the indices of the /// elements to remove. /// - Returns: A collection of the elements that are not in `subranges`. /// /// - Complexity: O(*n*), where *n* is the length of the collection. @available(SwiftStdlib 6.0, *) public func removingSubranges( _ subranges: RangeSet ) -> DiscontiguousSlice { let inversion = subranges._inverted(within: self) return self[inversion] } }