├── .gitignore ├── LICENSE.txt ├── Package.swift ├── README.md ├── Sources ├── SE0270_RangeSet │ ├── CollectionExtensions.swift │ ├── DiscontiguousSlice.swift │ ├── Pair.swift │ ├── Partition.swift │ ├── RangeSet.swift │ └── RangeSetStorage.swift └── TestHelpers │ ├── COWLoggingArray.swift │ └── XCTestExtensions.swift └── Tests └── SE0270_RangeSet_Tests ├── CollectionExtensionsTests.swift └── RangeSetTests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.swiftpm 3 | /.build 4 | /Packages 5 | /*.xcodeproj 6 | xcuserdata/ 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | 204 | 205 | ## Runtime Library Exception to the Apache 2.0 License: ## 206 | 207 | 208 | As an exception, if you use this Software to compile your source code and 209 | portions of this Software are embedded into the binary product as a result, 210 | you may redistribute such product without providing attribution as would 211 | otherwise be required by Sections 4(a), 4(b) and 4(d) of the License. 212 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | //===----------------------------------------------------------*- swift -*-===// 3 | // 4 | // This source file is part of the Swift open source project 5 | // 6 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 7 | // Licensed under Apache License v2.0 with Runtime Library Exception 8 | // 9 | // See https://swift.org/LICENSE.txt for license information 10 | // 11 | //===----------------------------------------------------------------------===// 12 | 13 | import PackageDescription 14 | 15 | let package = Package( 16 | name: "swift-se0270-range-set", 17 | products: [ 18 | .library( 19 | name: "SE0270_RangeSet", 20 | targets: ["SE0270_RangeSet"]), 21 | ], 22 | targets: [ 23 | .target( 24 | name: "SE0270_RangeSet", 25 | dependencies: []), 26 | .target( 27 | name: "TestHelpers", 28 | dependencies: ["SE0270_RangeSet"]), 29 | .testTarget( 30 | name: "SE0270_RangeSet_Tests", 31 | dependencies: ["SE0270_RangeSet", "TestHelpers"]), 32 | ] 33 | ) 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SE0270_RangeSet 2 | 3 | **SE0270_RangeSet** is a standalone library that implements the Swift Evolution proposal 4 | [SE-0270: Add Collection Operations on Noncontiguous Elements][proposal]. 5 | You can use this package independently, 6 | or as part of the [standard library preview package][stdlib-preview]. 7 | 8 | ## Functionality 9 | 10 | **SE0270_RangeSet** provides operations on noncontiguous subranges of collections, 11 | such as `subranges(where:)` and `moveSubranges(_:to:)`, 12 | as well as the supporting `RangeSet` type. 13 | 14 | ```swift 15 | import SE0270_RangeSet 16 | 17 | var numbers = [10, 12, -5, 14, -3, -9, 15] 18 | let negatives = numbers.subranges(where: { $0 < 0 }) 19 | // numbers[negatives].count == 3 20 | 21 | numbers.moveSubranges(negatives, to: 0) 22 | // numbers == [-5, -3, -9, 10, 12, 14, 15] 23 | ``` 24 | 25 | ## Usage 26 | 27 | You can add this library as a dependency to any Swift package. 28 | Add this line to the `dependencies` parameter in your `Package.swift` file: 29 | 30 | ```swift 31 | .package( 32 | url: "https://github.com/apple/swift-se0270-range-set", 33 | from: "1.0.0"), 34 | ``` 35 | 36 | Next, add the module as a dependency for your targets that will use the library: 37 | 38 | ```swift 39 | .product(name: "SE0270_RangeSet", package: "swift-se0270-range-set"), 40 | ``` 41 | 42 | You can now use `import SE0270_RangeSet` to make the library available in any Swift file. 43 | 44 | ## Contributing 45 | 46 | We are no longer taking contributions to this repo. Please see the 47 | [guide for Contributing to Swift][contributing] for other opportunities within the Swift 48 | project. Thanks to all past contributors! 49 | 50 | 51 | [proposal]: https://github.com/apple/swift-evolution/blob/master/proposals/0270-rangeset-and-collection-operations.md 52 | [stdlib-preview]: https://github.com/apple/swift-standard-library-preview 53 | [user-forums]: https://forums.swift.org/c/swift-users/ 54 | [bugs]: https://bugs.swift.org 55 | [evolution-process]: https://github.com/apple/swift-evolution/blob/master/process.md 56 | [contributing]: https://swift.org/contributing 57 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/CollectionExtensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | // MARK: Subscripts 13 | 14 | extension Collection { 15 | /// Accesses a view of this collection with the elements at the given 16 | /// indices. 17 | /// 18 | /// - Parameter subranges: The indices of the elements to retrieve from this 19 | /// collection. 20 | /// - Returns: A collection of the elements at the positions in `subranges`. 21 | /// 22 | /// - Complexity: O(1) 23 | public subscript(subranges: RangeSet) -> DiscontiguousSlice { 24 | DiscontiguousSlice(base: self, subranges: subranges) 25 | } 26 | } 27 | 28 | extension MutableCollection { 29 | /// Accesses a mutable view of this collection with the elements at the 30 | /// given indices. 31 | /// 32 | /// - Parameter subranges: The ranges of the elements to retrieve from this 33 | /// collection. 34 | /// - Returns: A collection of the elements at the positions in `subranges`. 35 | /// 36 | /// - Complexity: O(1) to access the elements, O(*m*) to mutate the 37 | /// elements at the positions in `subranges`, where *m* is the number of 38 | /// elements indicated by `subranges`. 39 | public subscript(subranges: RangeSet) -> DiscontiguousSlice { 40 | get { 41 | DiscontiguousSlice(base: self, subranges: subranges) 42 | } 43 | set { 44 | for i in newValue.indices where subranges.contains(i.base) { 45 | self[i.base] = newValue[i] 46 | } 47 | } 48 | } 49 | } 50 | 51 | // MARK: - moveSubranges(_:to:) 52 | 53 | extension MutableCollection { 54 | /// Moves the elements in the given subranges to just before the element at 55 | /// the specified index. 56 | /// 57 | /// This example finds all the uppercase letters in the array and then 58 | /// moves them to between `"i"` and `"j"`. 59 | /// 60 | /// var letters = Array("ABCdeFGhijkLMNOp") 61 | /// let uppercaseRanges = letters.subranges(where: { $0.isUppercase }) 62 | /// let rangeOfUppercase = letters.moveSubranges(uppercaseRanges, to: 10) 63 | /// // String(letters) == "dehiABCFGLMNOjkp" 64 | /// // rangeOfUppercase == 4..<13 65 | /// 66 | /// - Parameters: 67 | /// - subranges: The subranges of the elements to move. 68 | /// - insertionPoint: The index to use as the destination of the elements. 69 | /// - Returns: The new bounds of the moved elements. 70 | /// 71 | /// - Complexity: O(*n* log *n*) where *n* is the length of the collection. 72 | @discardableResult 73 | public mutating func moveSubranges( 74 | _ subranges: RangeSet, to insertionPoint: Index 75 | ) -> Range { 76 | let lowerCount = distance(from: startIndex, to: insertionPoint) 77 | let upperCount = distance(from: insertionPoint, to: endIndex) 78 | let start = _indexedStablePartition( 79 | count: lowerCount, 80 | range: startIndex.. = ["a", "e", "i", "o", "u"] 100 | /// let vowelIndices = str.subranges(where: { vowels.contains($0) }) 101 | /// 102 | /// str.removeSubranges(vowelIndices) 103 | /// // str == "Th rn n Spn stys mnly n th pln." 104 | /// 105 | /// - Parameter subranges: The indices of the elements to remove. 106 | /// 107 | /// - Complexity: O(*n*), where *n* is the length of the collection. 108 | public mutating func removeSubranges(_ subranges: RangeSet) { 109 | guard !subranges.isEmpty else { 110 | return 111 | } 112 | 113 | let inversion = subranges._inverted(within: self) 114 | var result = Self() 115 | for range in inversion.ranges { 116 | result.append(contentsOf: self[range]) 117 | } 118 | self = result 119 | } 120 | } 121 | 122 | extension MutableCollection where Self: RangeReplaceableCollection { 123 | /// Removes the elements at the given indices. 124 | /// 125 | /// For example, this code sample finds the indices of all the negative 126 | /// numbers in the array, and then removes those values. 127 | /// 128 | /// var numbers = [5, 7, -3, -8, 11, 2, -1, 6] 129 | /// let negativeIndices = numbers.subranges(where: { $0 < 0 }) 130 | /// 131 | /// numbers.removeSubranges(negativeIndices) 132 | /// // numbers == [5, 7, 11, 2, 6] 133 | /// 134 | /// - Parameter subranges: The indices of the elements to remove. 135 | /// 136 | /// - Complexity: O(*n*), where *n* is the length of the collection. 137 | public mutating func removeSubranges(_ subranges: RangeSet) { 138 | guard let firstRange = subranges.ranges.first else { 139 | return 140 | } 141 | 142 | var endOfElementsToKeep = firstRange.lowerBound 143 | var firstUnprocessed = firstRange.upperBound 144 | 145 | // This performs a half-stable partition based on the ranges in 146 | // `indices`. At all times, the collection is divided into three 147 | // regions: 148 | // 149 | // - `self[.. = ["a", "e", "i", "o", "u"] 191 | /// let vowelIndices = str.subranges(where: { vowels.contains($0) }) 192 | /// 193 | /// let disemvoweled = str.removingSubranges(vowelIndices) 194 | /// print(String(disemvoweled)) 195 | /// // Prints "Th rn n Spn stys mnly n th pln." 196 | /// 197 | /// - Parameter subranges: A range set representing the indices of the 198 | /// elements to remove. 199 | /// - Returns: A collection of the elements that are not in `subranges`. 200 | /// 201 | /// - Complexity: O(*n*), where *n* is the length of the collection. 202 | public func removingSubranges( 203 | _ subranges: RangeSet 204 | ) -> DiscontiguousSlice { 205 | let inversion = subranges._inverted(within: self) 206 | return self[inversion] 207 | } 208 | } 209 | 210 | // MARK: - subranges(where:) / subranges(of:) 211 | 212 | extension Collection { 213 | /// Returns the indices of all the elements that match the given predicate. 214 | /// 215 | /// For example, you can use this method to find all the places that a 216 | /// vowel occurs in a string. 217 | /// 218 | /// let str = "Fresh cheese in a breeze" 219 | /// let vowels: Set = ["a", "e", "i", "o", "u"] 220 | /// let allTheVowels = str.subranges(where: { vowels.contains($0) }) 221 | /// // str[allTheVowels].count == 9 222 | /// 223 | /// - Parameter predicate: A closure that takes an element as its argument 224 | /// and returns a Boolean value that indicates whether the passed element 225 | /// represents a match. 226 | /// - Returns: A set of the indices of the elements for which `predicate` 227 | /// returns `true`. 228 | /// 229 | /// - Complexity: O(*n*), where *n* is the length of the collection. 230 | public func subranges(where predicate: (Element) throws -> Bool) rethrows 231 | -> RangeSet 232 | { 233 | if isEmpty { return RangeSet() } 234 | 235 | var result = RangeSet() 236 | var i = startIndex 237 | while i != endIndex { 238 | let next = index(after: i) 239 | if try predicate(self[i]) { 240 | result._append(i.. RangeSet { 266 | subranges(where: { $0 == element }) 267 | } 268 | } 269 | 270 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/DiscontiguousSlice.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | /// A collection wrapper that provides access to the elements of a collection, 13 | /// indexed by a set of indices. 14 | public struct DiscontiguousSlice { 15 | /// The collection that the indexed collection wraps. 16 | public var base: Base 17 | 18 | /// The set of subranges that are available through this discontiguous slice. 19 | public var subranges: RangeSet 20 | } 21 | 22 | extension DiscontiguousSlice { 23 | /// A position in an `DiscontiguousSlice`. 24 | public struct Index: Comparable { 25 | /// The index of the range that contains `base`. 26 | internal var _rangeOffset: Int 27 | 28 | /// The position of this index in the base collection. 29 | public var base: Base.Index 30 | 31 | public static func < (lhs: Index, rhs: Index) -> Bool { 32 | lhs.base < rhs.base 33 | } 34 | } 35 | } 36 | 37 | extension DiscontiguousSlice.Index: Hashable where Base.Index: Hashable {} 38 | 39 | extension DiscontiguousSlice: Collection { 40 | public typealias SubSequence = Self 41 | 42 | public var startIndex: Index { 43 | subranges.isEmpty 44 | ? endIndex 45 | : Index(_rangeOffset: 0, base: subranges._ranges[0].lowerBound) 46 | } 47 | 48 | public var endIndex: Index { 49 | Index(_rangeOffset: subranges._ranges.endIndex, base: base.endIndex) 50 | } 51 | 52 | public func index(after i: Index) -> Index { 53 | let nextIndex = base.index(after: i.base) 54 | if subranges._ranges[i._rangeOffset].contains(nextIndex) { 55 | return Index(_rangeOffset: i._rangeOffset, base: nextIndex) 56 | } 57 | 58 | let nextOffset = i._rangeOffset + 1 59 | if nextOffset < subranges._ranges.endIndex { 60 | return Index( 61 | _rangeOffset: nextOffset, 62 | base: subranges._ranges[nextOffset].lowerBound) 63 | } else { 64 | return endIndex 65 | } 66 | } 67 | 68 | public subscript(i: Index) -> Base.Element { 69 | base[i.base] 70 | } 71 | 72 | public subscript(bounds: Range) -> DiscontiguousSlice { 73 | let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base 74 | let subset = subranges.intersection(RangeSet(baseBounds)) 75 | return DiscontiguousSlice(base: base, subranges: subset) 76 | } 77 | } 78 | 79 | extension DiscontiguousSlice { 80 | public var count: Int { 81 | var c = 0 82 | for range in subranges._ranges { 83 | c += base.distance(from: range.lowerBound, to: range.upperBound) 84 | } 85 | return c 86 | } 87 | 88 | public __consuming func _copyToContiguousArray() -> ContiguousArray { 89 | var result: ContiguousArray = [] 90 | for range in subranges._ranges { 91 | result.append(contentsOf: base[range]) 92 | } 93 | return result 94 | } 95 | } 96 | 97 | extension DiscontiguousSlice: BidirectionalCollection 98 | where Base: BidirectionalCollection 99 | { 100 | public func index(before i: Index) -> Index { 101 | precondition(i != startIndex, "Can't move index before startIndex") 102 | 103 | if i == endIndex || i.base == subranges._ranges[i._rangeOffset].lowerBound { 104 | let offset = i._rangeOffset - 1 105 | return Index( 106 | _rangeOffset: offset, 107 | base: base.index(before: subranges._ranges[offset].upperBound)) 108 | } 109 | 110 | return Index( 111 | _rangeOffset: i._rangeOffset, 112 | base: base.index(before: i.base)) 113 | } 114 | } 115 | 116 | extension DiscontiguousSlice: MutableCollection where Base: MutableCollection { 117 | public subscript(i: Index) -> Base.Element { 118 | get { 119 | base[i.base] 120 | } 121 | set { 122 | base[i.base] = newValue 123 | } 124 | } 125 | 126 | public subscript(bounds: Range) -> DiscontiguousSlice { 127 | get { 128 | let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base 129 | let subset = subranges.intersection(RangeSet(baseBounds)) 130 | return DiscontiguousSlice(base: base, subranges: subset) 131 | } 132 | set { 133 | let baseBounds = bounds.lowerBound.base ..< bounds.upperBound.base 134 | let subset = subranges.intersection(RangeSet(baseBounds)) 135 | for i in newValue.indices where subset.contains(i.base) { 136 | base[i.base] = newValue[i] 137 | } 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/Pair.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | /// A collection of two elements, to avoid heap allocation when calling 13 | /// `replaceSubrange` with just two elements. 14 | internal struct Pair: RandomAccessCollection { 15 | var pair: (first: Element, second: Element) 16 | 17 | init(_ first: Element, _ second: Element) { 18 | self.pair = (first, second) 19 | } 20 | 21 | var startIndex: Int { 0 } 22 | var endIndex: Int { 2 } 23 | 24 | subscript(position: Int) -> Element { 25 | get { 26 | switch position { 27 | case 0: return pair.first 28 | case 1: return pair.second 29 | default: fatalError("Index '\(position)' is out of range") 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/Partition.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | // MARK: _rotate(in:shiftingToStart:) 13 | 14 | extension MutableCollection { 15 | /// Rotates the elements of the collection so that the element at `middle` 16 | /// ends up first. 17 | /// 18 | /// - Returns: The new index of the element that was first pre-rotation. 19 | /// 20 | /// - Complexity: O(*n*) 21 | @discardableResult 22 | internal mutating func _rotate(in subrange: Range, shiftingToStart middle: Index) -> Index { 23 | var m = middle, s = subrange.lowerBound 24 | let e = subrange.upperBound 25 | 26 | // Handle the trivial cases 27 | if s == m { return e } 28 | if m == e { return s } 29 | 30 | // We have two regions of possibly-unequal length that need to be 31 | // exchanged. The return value of this method is going to be the 32 | // position following that of the element that is currently last 33 | // (element j). 34 | // 35 | // [a b c d e f g|h i j] or [a b c|d e f g h i j] 36 | // ^ ^ ^ ^ ^ ^ 37 | // s m e s m e 38 | // 39 | var ret = e // start with a known incorrect result. 40 | while true { 41 | // Exchange the leading elements of each region (up to the 42 | // length of the shorter region). 43 | // 44 | // [a b c d e f g|h i j] or [a b c|d e f g h i j] 45 | // ^^^^^ ^^^^^ ^^^^^ ^^^^^ 46 | // [h i j d e f g|a b c] or [d e f|a b c g h i j] 47 | // ^ ^ ^ ^ ^ ^ ^ ^ 48 | // s s1 m m1/e s s1/m m1 e 49 | // 50 | let (s1, m1) = _swapNonemptySubrangePrefixes(s.., _ rhs: Range 101 | ) -> (Index, Index) { 102 | assert(!lhs.isEmpty) 103 | assert(!rhs.isEmpty) 104 | 105 | var p = lhs.lowerBound 106 | var q = rhs.lowerBound 107 | repeat { 108 | swapAt(p, q) 109 | formIndex(after: &p) 110 | formIndex(after: &q) 111 | } 112 | while p != lhs.upperBound && q != rhs.upperBound 113 | return (p, q) 114 | } 115 | } 116 | 117 | // MARK: - _stablePartition(count:range:by:) 118 | 119 | extension MutableCollection { 120 | /// Moves all elements satisfying `belongsInSecondPartition` into a suffix 121 | /// of the collection, preserving their relative order, and returns the 122 | /// start of the resulting suffix. 123 | /// 124 | /// - Complexity: O(*n* log *n*) where *n* is the number of elements. 125 | /// - Precondition: 126 | /// `n == distance(from: range.lowerBound, to: range.upperBound)` 127 | internal mutating func _stablePartition( 128 | count n: Int, 129 | range: Range, 130 | by belongsInSecondPartition: (Element) throws-> Bool 131 | ) rethrows -> Index { 132 | if n == 0 { return range.lowerBound } 133 | if n == 1 { 134 | return try belongsInSecondPartition(self[range.lowerBound]) 135 | ? range.lowerBound 136 | : range.upperBound 137 | } 138 | let h = n / 2, i = index(range.lowerBound, offsetBy: h) 139 | let j = try _stablePartition( 140 | count: h, 141 | range: range.lowerBound.., 160 | by belongsInSecondPartition: (Index) throws-> Bool 161 | ) rethrows -> Index { 162 | if n == 0 { return range.lowerBound } 163 | if n == 1 { 164 | return try belongsInSecondPartition(range.lowerBound) 165 | ? range.lowerBound 166 | : range.upperBound 167 | } 168 | let h = n / 2, i = index(range.lowerBound, offsetBy: h) 169 | let j = try _indexedStablePartition( 170 | count: h, 171 | range: range.lowerBound.. Bool 200 | ) rethrows -> Index { 201 | var n = count 202 | var l = startIndex 203 | 204 | while n > 0 { 205 | let half = n / 2 206 | let mid = index(l, offsetBy: half) 207 | if try predicate(self[mid]) { 208 | n = half 209 | } else { 210 | l = index(after: mid) 211 | n -= half + 1 212 | } 213 | } 214 | return l 215 | } 216 | } 217 | 218 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/RangeSet.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | /// A set of values of any comparable type, represented by ranges. 13 | /// 14 | /// You can use a range set to efficiently represent a set of `Comparable` 15 | /// values that spans any number of discontiguous ranges. Range sets are 16 | /// commonly used to represent multiple subranges of a collection, by storing 17 | /// ranges of a collection's index type. 18 | /// 19 | /// In this example, `negativeSubranges` is a range set representing the 20 | /// locations of all the negative values in `numbers`: 21 | /// 22 | /// var numbers = [10, 12, -5, 14, -3, -9, 15] 23 | /// let negativeSubranges = numbers.subranges(where: { $0 < 0 }) 24 | /// // numbers[negativeSubranges].count == 3 25 | /// 26 | /// numbers.moveSubranges(negativeSubranges, to: 0) 27 | /// // numbers == [-5, -3, -9, 10, 12, 14, 15] 28 | public struct RangeSet { 29 | internal var _ranges = _RangeSetStorage() 30 | 31 | /// Creates an empty range set. 32 | public init() {} 33 | 34 | /// Creates a range set containing the given range. 35 | /// 36 | /// - Parameter range: The range to use for the new range set. 37 | public init(_ range: Range) { 38 | if !range.isEmpty { 39 | self._ranges = _RangeSetStorage(range) 40 | } 41 | } 42 | 43 | /// Creates a range set containing the values in the given ranges. 44 | /// 45 | /// Any empty ranges in `ranges` are ignored, and non-empty ranges are merged 46 | /// to eliminate any overlaps. As such, the `ranges` collection in the 47 | /// resulting range set may not be equivalent to the sequence of ranges 48 | /// passed to this initializer. 49 | /// 50 | /// - Parameter ranges: The ranges to use for the new range set. 51 | public init(_ ranges: S) where S.Element == Range { 52 | for range in ranges { 53 | insert(contentsOf: range) 54 | } 55 | } 56 | 57 | /// Checks the invariants of `_ranges`. 58 | /// 59 | /// The ranges stored by a range set are never empty, never overlap, 60 | /// and are always stored in ascending order when comparing their lower 61 | /// or upper bounds. In addition to not overlapping, no two consecutive 62 | /// ranges share an upper and lower bound — `[0..<5, 5..<10]` is ill-formed, 63 | /// and would instead be represented as `[0..<10]`. 64 | internal func _checkInvariants() { 65 | for (a, b) in zip(ranges, ranges.dropFirst()) { 66 | precondition(!a.isEmpty && !b.isEmpty, "Empty range in range set") 67 | precondition( 68 | a.upperBound < b.lowerBound, 69 | "Out of order/overlapping ranges in range set") 70 | } 71 | } 72 | 73 | /// Creates a new range set from `ranges`, which satisfies the range set 74 | /// invariants. 75 | internal init(_orderedRanges ranges: [Range]) { 76 | self._ranges = _RangeSetStorage(ranges) 77 | _checkInvariants() 78 | } 79 | 80 | /// A Boolean value indicating whether the range set is empty. 81 | public var isEmpty: Bool { 82 | _ranges.isEmpty 83 | } 84 | 85 | /// Returns a Boolean value indicating whether the given value is 86 | /// contained by the ranges in the range set. 87 | /// 88 | /// - Parameter value: The value to look for in the range set. 89 | /// - Returns: `true` if `value` is contained by a range in the range set; 90 | /// otherwise, `false`. 91 | /// 92 | /// - Complexity: O(log *n*), where *n* is the number of ranges in the 93 | /// range set. 94 | public func contains(_ value: Bound) -> Bool { 95 | let i = _ranges._partitioningIndex { $0.upperBound > value } 96 | return i == _ranges.endIndex 97 | ? false 98 | : _ranges[i].lowerBound <= value 99 | } 100 | 101 | /// Returns a range indicating the existing ranges that `range` overlaps 102 | /// with. 103 | /// 104 | /// For example, if `self` is `[0..<5, 10..<15, 20..<25, 30..<35]`, then: 105 | /// 106 | /// - `_indicesOfRange(12..<14) == 1..<2` 107 | /// - `_indicesOfRange(12..<19) == 1..<2` 108 | /// - `_indicesOfRange(17..<19) == 2..<2` 109 | /// - `_indicesOfRange(12..<22) == 1..<3` 110 | func _indicesOfRange(_ range: Range) -> Range { 111 | precondition(!range.isEmpty) 112 | precondition(!_ranges.isEmpty) 113 | precondition(range.lowerBound <= _ranges.last!.upperBound) 114 | precondition(range.upperBound >= _ranges.first!.lowerBound) 115 | 116 | // The beginning index for the position of `range` is the first range 117 | // with an upper bound larger than `range`'s lower bound. The range 118 | // at this position may or may not overlap `range`. 119 | let beginningIndex = _ranges 120 | ._partitioningIndex { $0.upperBound >= range.lowerBound } 121 | 122 | // The ending index for `range` is the first range with a lower bound 123 | // greater than `range`'s upper bound. If this is the same as 124 | // `beginningIndex`, than `range` doesn't overlap any of the existing 125 | // ranges. If this is `ranges.endIndex`, then `range` overlaps the 126 | // rest of the ranges. Otherwise, `range` overlaps one or 127 | // more ranges in the set. 128 | let endingIndex = _ranges[beginningIndex...] 129 | ._partitioningIndex { $0.lowerBound > range.upperBound } 130 | 131 | return beginningIndex ..< endingIndex 132 | } 133 | 134 | /// Inserts a non-empty range that is known to be greater than all the 135 | /// elements in the set so far. 136 | /// 137 | /// - Precondition: The range set must be empty, or else 138 | /// `ranges.last!.upperBound <= range.lowerBound`. 139 | /// - Precondition: `range` must not be empty. 140 | internal mutating func _append(_ range: Range) { 141 | precondition(_ranges.isEmpty 142 | || _ranges.last!.upperBound <= range.lowerBound) 143 | precondition(!range.isEmpty) 144 | if _ranges.isEmpty { 145 | _ranges.append(range) 146 | } else if _ranges.last!.upperBound == range.lowerBound { 147 | _ranges[_ranges.count - 1] = 148 | _ranges[_ranges.count - 1].lowerBound ..< range.upperBound 149 | } else { 150 | _ranges.append(range) 151 | } 152 | } 153 | 154 | /// Inserts the given range into the range set. 155 | /// 156 | /// - Parameter range: The range to insert into the set. 157 | /// 158 | /// - Complexity: O(*n*), where *n* is the number of ranges in the range 159 | /// set. 160 | public mutating func insert(contentsOf range: Range) { 161 | // Shortcuts for the (literal) edge cases 162 | if range.isEmpty { return } 163 | guard !_ranges.isEmpty else { 164 | _ranges.append(range) 165 | return 166 | } 167 | guard range.lowerBound < _ranges.last!.upperBound else { 168 | _append(range) 169 | return 170 | } 171 | guard range.upperBound >= _ranges.first!.lowerBound else { 172 | _ranges.insert(range, at: 0) 173 | return 174 | } 175 | 176 | let indices = _indicesOfRange(range) 177 | 178 | // Non-overlapping is a simple insertion. 179 | guard !indices.isEmpty else { 180 | _ranges.insert(range, at: indices.lowerBound) 181 | return 182 | } 183 | 184 | // Find the lower and upper bounds of the overlapping ranges. 185 | let newLowerBound = Swift.min( 186 | _ranges[indices.lowerBound].lowerBound, 187 | range.lowerBound) 188 | let newUpperBound = Swift.max( 189 | _ranges[indices.upperBound - 1].upperBound, 190 | range.upperBound) 191 | _ranges.replaceSubrange( 192 | indices, 193 | with: CollectionOfOne(newLowerBound..) { 203 | // Shortcuts for the (literal) edge cases 204 | if range.isEmpty 205 | || _ranges.isEmpty 206 | || range.lowerBound >= _ranges.last!.upperBound 207 | || range.upperBound < _ranges.first!.lowerBound 208 | { return } 209 | 210 | let indices = _indicesOfRange(range) 211 | 212 | // No actual overlap, nothing to remove. 213 | if indices.isEmpty { return } 214 | 215 | let overlapsLowerBound = 216 | range.lowerBound > _ranges[indices.lowerBound].lowerBound 217 | let overlapsUpperBound = 218 | range.upperBound < _ranges[indices.upperBound - 1].upperBound 219 | 220 | switch (overlapsLowerBound, overlapsUpperBound) { 221 | case (false, false): 222 | _ranges.removeSubrange(indices) 223 | case (false, true): 224 | let newRange = 225 | range.upperBound..<_ranges[indices.upperBound - 1].upperBound 226 | _ranges.replaceSubrange(indices, with: CollectionOfOne(newRange)) 227 | case (true, false): 228 | let newRange = _ranges[indices.lowerBound].lowerBound.. 249 | 250 | public var startIndex: Int { _ranges.startIndex } 251 | public var endIndex: Int { _ranges.endIndex } 252 | 253 | public subscript(i: Int) -> Range { 254 | _ranges[i] 255 | } 256 | } 257 | 258 | /// A collection of the ranges that make up the range set. 259 | /// 260 | /// The ranges that you access by using `ranges` never overlap, are never 261 | /// empty, and are always in increasing order. 262 | public var ranges: Ranges { 263 | Ranges(_ranges: _ranges) 264 | } 265 | } 266 | 267 | // MARK: - Collection APIs 268 | 269 | extension RangeSet { 270 | /// Creates a new range set containing ranges that contain only the 271 | /// specified indices in the given collection. 272 | /// 273 | /// - Parameters: 274 | /// - index: The index to include in the range set. `index` must be a 275 | /// valid index of `collection` that isn't the collection's `endIndex`. 276 | /// - collection: The collection that contains `index`. 277 | public init(_ indices: S, within collection: C) 278 | where S: Sequence, C: Collection, S.Element == C.Index, C.Index == Bound 279 | { 280 | for i in indices { 281 | self.insert(i, within: collection) 282 | } 283 | } 284 | 285 | /// Inserts a range that contains only the specified index into the range 286 | /// set. 287 | /// 288 | /// - Parameters: 289 | /// - index: The index to insert into the range set. `index` must be a 290 | /// valid index of `collection` that isn't the collection's `endIndex`. 291 | /// - collection: The collection that contains `index`. 292 | /// 293 | /// - Complexity: O(*n*), where *n* is the number of ranges in the range 294 | /// set. 295 | public mutating func insert(_ index: Bound, within collection: C) 296 | where C: Collection, C.Index == Bound 297 | { 298 | insert(contentsOf: index ..< collection.index(after: index)) 299 | } 300 | 301 | /// Removes the range that contains only the specified index from the range 302 | /// set. 303 | /// 304 | /// - Parameters: 305 | /// - index: The index to remove from the range set. `index` must be a 306 | /// valid index of `collection` that isn't the collection's `endIndex`. 307 | /// - collection: The collection that contains `index`. 308 | /// 309 | /// - Complexity: O(*n*), where *n* is the number of ranges in the range 310 | /// set. 311 | public mutating func remove(_ index: Bound, within collection: C) 312 | where C: Collection, C.Index == Bound 313 | { 314 | remove(contentsOf: index ..< collection.index(after: index)) 315 | } 316 | 317 | /// Returns a range set that represents all the elements in the given 318 | /// collection that aren't represented by this range set. 319 | /// 320 | /// - Parameter collection: The collection that the range set is relative 321 | /// to. 322 | /// - Returns: A new range set that represents the elements in `collection` 323 | /// that aren't represented by this range set. 324 | /// 325 | /// - Complexity: O(*n*), where *n* is the number of ranges in the range 326 | /// set. 327 | internal func _inverted(within collection: C) -> RangeSet 328 | where C: Collection, C.Index == Bound 329 | { 330 | return _gaps( 331 | boundedBy: collection.startIndex..) -> RangeSet { 337 | guard !_ranges.isEmpty else { return RangeSet(bounds) } 338 | guard let start = _ranges.firstIndex(where: { $0.lowerBound >= bounds.lowerBound }) 339 | else { return RangeSet() } 340 | guard let end = _ranges.lastIndex(where: { $0.upperBound <= bounds.upperBound }) 341 | else { return RangeSet() } 342 | 343 | var result = RangeSet() 344 | var low = bounds.lowerBound 345 | for range in _ranges[start...end] { 346 | result.insert(contentsOf: low..) { 363 | for range in other._ranges { 364 | insert(contentsOf: range) 365 | } 366 | } 367 | 368 | /// Removes the contents of this range set that aren't also in the given 369 | /// range set. 370 | /// 371 | /// - Parameter other: A range set to intersect with. 372 | public mutating func formIntersection(_ other: RangeSet) { 373 | self = self.intersection(other) 374 | } 375 | 376 | /// Removes the contents of this range set that are also in the given set 377 | /// and adds the contents of the given set that are not already in this 378 | /// range set. 379 | /// 380 | /// - Parameter other: A range set to perform a symmetric difference against. 381 | public mutating func formSymmetricDifference( 382 | _ other: __owned RangeSet 383 | ) { 384 | self = self.symmetricDifference(other) 385 | } 386 | 387 | /// Removes the contents of the given range set from this range set. 388 | /// 389 | /// - Parameter other: A range set to subtract from this one. 390 | public mutating func subtract(_ other: RangeSet) { 391 | for range in other._ranges { 392 | remove(contentsOf: range) 393 | } 394 | } 395 | 396 | /// Returns a new range set containing the contents of both this set and the 397 | /// given set. 398 | /// 399 | /// - Parameter other: The range set to merge with this one. 400 | /// - Returns: A new range set. 401 | public __consuming func union( 402 | _ other: __owned RangeSet 403 | ) -> RangeSet { 404 | var result = self 405 | result.formUnion(other) 406 | return result 407 | } 408 | 409 | /// Returns a new range set containing the contents of both this set and the 410 | /// given set. 411 | /// 412 | /// - Parameter other: The range set to merge with this one. 413 | /// - Returns: A new range set. 414 | public __consuming func intersection( 415 | _ other: RangeSet 416 | ) -> RangeSet { 417 | var otherRangeIndex = 0 418 | var result: [Range] = [] 419 | 420 | // Considering these two range sets: 421 | // 422 | // self = [0..<5, 9..<14] 423 | // other = [1..<3, 4..<6, 8..<12] 424 | // 425 | // `self.intersection(other)` looks like this, where x's cover the 426 | // ranges in `self`, y's cover the ranges in `other`, and z's cover the 427 | // resulting ranges: 428 | // 429 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 430 | // xxxxxxxxxxxxxxxxxxx__ xxxxxxxxxxxxxxxxxxx__ 431 | // yyyyyyy__ yyyyyyy__ yyyyyyyyyyyyyyy__ 432 | // zzzzzzz__ zzz__ zzzzzzzzzzz__ 433 | // 434 | // The same, but for `other.intersection(self)`: 435 | // 436 | // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 437 | // xxxxxxx__ xxxxxxx__ xxxxxxxxxxxxxxx__ 438 | // yyyyyyyyyyyyyyyyyyy__ yyyyyyyyyyyyyyyyyyy__ 439 | // zzzzzzz__ zzz__ zzzzzzzzzzz__ 440 | 441 | for currentRange in _ranges { 442 | // Search forward in `other` until finding either an overlapping 443 | // range or one that is strictly higher than this range. 444 | while otherRangeIndex < other._ranges.endIndex && 445 | other._ranges[otherRangeIndex].upperBound <= currentRange.lowerBound 446 | { 447 | otherRangeIndex += 1 448 | } 449 | 450 | // For each range in `other` that overlaps with the current range 451 | // in `self`, append the intersection to the result. 452 | while otherRangeIndex < other._ranges.endIndex && 453 | other._ranges[otherRangeIndex].lowerBound < currentRange.upperBound 454 | { 455 | let lower = Swift.max( 456 | other._ranges[otherRangeIndex].lowerBound, 457 | currentRange.lowerBound) 458 | let upper = Swift.min( 459 | other._ranges[otherRangeIndex].upperBound, 460 | currentRange.upperBound) 461 | result.append(lower.. other._ranges[otherRangeIndex].upperBound 468 | else { 469 | break 470 | } 471 | otherRangeIndex += 1 472 | } 473 | } 474 | 475 | return RangeSet(_orderedRanges: result) 476 | } 477 | 478 | /// Returns a new range set representing the values in this range set or the 479 | /// given range set, but not both. 480 | /// 481 | /// - Parameter other: The range set to find a symmetric difference with. 482 | /// - Returns: A new range set. 483 | public __consuming func symmetricDifference( 484 | _ other: __owned RangeSet 485 | ) -> RangeSet { 486 | return union(other).subtracting(intersection(other)) 487 | } 488 | 489 | /// Returns a new set containing the contents of this range set that are not 490 | /// also in the given range set. 491 | /// 492 | /// - Parameter other: The range set to subtract. 493 | /// - Returns: A new range set. 494 | public func subtracting(_ other: RangeSet) -> RangeSet { 495 | var result = self 496 | result.subtract(other) 497 | return result 498 | } 499 | 500 | /// Returns a Boolean value that indicates whether this range set is a 501 | /// subset of the given set. 502 | /// 503 | /// - Parameter other: A range set to compare against. 504 | /// - Returns: `true` if this range set is a subset of `other`; 505 | /// otherwise, `false`. 506 | public func isSubset(of other: RangeSet) -> Bool { 507 | self.intersection(other) == self 508 | } 509 | 510 | /// Returns a Boolean value that indicates whether this range set is a 511 | /// superset of the given set. 512 | /// 513 | /// - Parameter other: A range set to compare against. 514 | /// - Returns: `true` if this range set is a superset of `other`; 515 | /// otherwise, `false`. 516 | public func isSuperset(of other: RangeSet) -> Bool { 517 | other.isSubset(of: self) 518 | } 519 | 520 | /// Returns a Boolean value that indicates whether this range set is a 521 | /// strict subset of the given set. 522 | /// 523 | /// - Parameter other: A range set to compare against. 524 | /// - Returns: `true` if this range set is a strict subset of `other`; 525 | /// otherwise, `false`. 526 | public func isStrictSubset(of other: RangeSet) -> Bool { 527 | self != other && isSubset(of: other) 528 | } 529 | 530 | /// Returns a Boolean value that indicates whether this range set is a 531 | /// strict superset of the given set. 532 | /// 533 | /// - Parameter other: A range set to compare against. 534 | /// - Returns: `true` if this range set is a strict superset of `other`; 535 | /// otherwise, `false`. 536 | public func isStrictSuperset(of other: RangeSet) -> Bool { 537 | other.isStrictSubset(of: self) 538 | } 539 | } 540 | 541 | extension RangeSet: CustomStringConvertible { 542 | public var description: String { 543 | let rangesDescription = _ranges 544 | .map { r in "\(r.lowerBound)..<\(r.upperBound)" } 545 | .joined(separator: ", ") 546 | return "RangeSet(\(rangesDescription))" 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /Sources/SE0270_RangeSet/RangeSetStorage.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | struct _RangeSetStorage { 13 | fileprivate enum _Storage { 14 | case empty 15 | case singleRange(high: Int, low: Int32) 16 | case variadic([Range]) 17 | } 18 | 19 | fileprivate var _storage: _Storage 20 | 21 | init() { 22 | _storage = .empty 23 | } 24 | 25 | init(_ range: Range) { 26 | if let intRange = range as? Range, 27 | let lowerBound = Int32(exactly: intRange.lowerBound) 28 | { 29 | _storage = .singleRange(high: intRange.upperBound, low: lowerBound) 30 | } else { 31 | _storage = .variadic([range]) 32 | } 33 | } 34 | 35 | init(_ ranges: [Range]) { 36 | _storage = .variadic(ranges) 37 | } 38 | 39 | func unsafeRange(low: Int32, high: Int) -> Range { 40 | unsafeBitCast(Int(low)...self) 41 | } 42 | } 43 | 44 | // _RangeSetStorage has custom Equatable (and therefore Hashable) 45 | // conformance, since the same "value" can be represented by different 46 | // storage structures. For example, `.empty` and `.variadic([])` are 47 | // equivalent, but the synthesized conformance treats them as distinct. 48 | // The same holds with the `singleRange` representation and `variadic` 49 | // with a single-element array. 50 | 51 | extension _RangeSetStorage: Equatable { 52 | static func == (lhs: _RangeSetStorage, rhs: _RangeSetStorage) -> Bool { 53 | switch (lhs._storage, rhs._storage) { 54 | case (.empty, .empty): 55 | return true 56 | case (.empty, .singleRange), (.singleRange, .empty): 57 | return false 58 | case let (.empty, .variadic(ranges)), 59 | let (.variadic(ranges), .empty): 60 | return ranges.isEmpty 61 | 62 | case let (.singleRange(lhsHigh, lhsLow), .singleRange(rhsHigh, rhsLow)): 63 | return (lhsLow, lhsHigh) == (rhsLow, rhsHigh) 64 | 65 | case let (.singleRange(high, low), .variadic(ranges)), 66 | let (.variadic(ranges), .singleRange(high, low)): 67 | return ranges.count == 1 && 68 | (ranges[0] as! Range) == Int(low).. Range { 96 | get { 97 | switch _storage { 98 | case .empty: fatalError("Can't access elements of empty storage") 99 | case let .singleRange(high, low): 100 | assert(T.self == Int.self) 101 | return unsafeRange(low: low, high: high) 102 | case let .variadic(ranges): 103 | return ranges[i] 104 | } 105 | } 106 | set { 107 | switch _storage { 108 | case .empty: fatalError("Can't access elements of empty storage") 109 | case .singleRange: 110 | assert(T.self == Int.self) 111 | let intRange = newValue as! Range 112 | if let lowerBound = Int32(exactly: intRange.lowerBound) { 113 | _storage = .singleRange(high: intRange.upperBound, low: lowerBound) 114 | } else { 115 | _storage = .variadic([newValue]) 116 | } 117 | case .variadic(var ranges): 118 | // Temporarily set `_storage` to empty so that `ranges` 119 | // remains uniquely referenced while mutating. 120 | _storage = .empty 121 | ranges[i] = newValue 122 | _storage = .variadic(ranges) 123 | } 124 | } 125 | } 126 | 127 | var count: Int { 128 | switch _storage { 129 | case .empty: return 0 130 | case .singleRange: return 1 131 | case let .variadic(ranges): return ranges.count 132 | } 133 | } 134 | } 135 | 136 | extension _RangeSetStorage: RangeReplaceableCollection { 137 | mutating func replaceSubrange(_ subrange: Range, with newElements: C) where C : Collection, C.Element == Element { 138 | switch _storage { 139 | case .empty: 140 | if !newElements.isEmpty { 141 | _storage = .variadic(Array(newElements)) 142 | } 143 | 144 | case .singleRange(high: let high, low: let low): 145 | switch (subrange.isEmpty, newElements.isEmpty) { 146 | case (false, true): 147 | // Replacing the single range with an empty collection. 148 | _storage = .empty 149 | 150 | case (false, false): 151 | // Replacing the single range with a non-empty collection; 152 | // promote to a variadic container. 153 | _storage = .variadic(Array(newElements)) 154 | 155 | case (true, true): 156 | // Inserting an empty collection; no-op. 157 | break 158 | 159 | case (true, false): 160 | // Inserting a non-empty collection either before or after 161 | // the existing single element. 162 | var ranges: [Range] 163 | if subrange.lowerBound == 0 { 164 | ranges = Array(newElements) 165 | ranges.append(unsafeRange(low: low, high: high)) 166 | } else { 167 | ranges = [unsafeRange(low: low, high: high)] 168 | ranges.append(contentsOf: newElements) 169 | } 170 | _storage = .variadic(ranges) 171 | } 172 | 173 | case .variadic(var ranges): 174 | // Temporarily set `_storage` to empty so that `ranges` 175 | // remains uniquely referenced while mutating. 176 | _storage = .empty 177 | ranges.replaceSubrange(subrange, with: newElements) 178 | _storage = .variadic(ranges) 179 | } 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /Sources/TestHelpers/COWLoggingArray.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | public var COWLoggingArray_CopyCount = 0 13 | 14 | public struct COWLoggingArray { 15 | var storage: Storage 16 | 17 | class Storage { 18 | var buffer: UnsafeMutableBufferPointer 19 | var count: Int 20 | var capacity: Int { 21 | buffer.count 22 | } 23 | 24 | init(capacity: Int) { 25 | self.buffer = .allocate(capacity: capacity) 26 | self.count = 0 27 | } 28 | 29 | deinit { 30 | buffer.baseAddress!.deinitialize(count: count) 31 | buffer.deallocate() 32 | } 33 | 34 | func cloned(capacity: Int? = nil) -> Storage { 35 | let newCapacity = Swift.max(capacity ?? self.capacity, self.capacity) 36 | let newStorage = Storage(capacity: newCapacity) 37 | newStorage.buffer.baseAddress! 38 | .initialize(from: buffer.baseAddress!, count: count) 39 | newStorage.count = count 40 | return newStorage 41 | } 42 | } 43 | 44 | mutating func _makeUnique() { 45 | if !isKnownUniquelyReferenced(&storage) { 46 | storage = storage.cloned() 47 | COWLoggingArray_CopyCount += 1 48 | } 49 | } 50 | } 51 | 52 | extension COWLoggingArray: RandomAccessCollection, RangeReplaceableCollection, 53 | MutableCollection, ExpressibleByArrayLiteral 54 | { 55 | public var count: Int { storage.count } 56 | public var startIndex: Int { 0 } 57 | public var endIndex: Int { count } 58 | 59 | public subscript(i: Int) -> Element { 60 | get { 61 | storage.buffer[i] 62 | } 63 | set { 64 | _makeUnique() 65 | storage.buffer[i] = newValue 66 | } 67 | } 68 | 69 | public init() { 70 | storage = Storage(capacity: 10) 71 | } 72 | 73 | public mutating func reserveCapacity(_ n: Int) { 74 | if !isKnownUniquelyReferenced(&storage) { 75 | COWLoggingArray_CopyCount += 1 76 | storage = storage.cloned(capacity: n) 77 | } else if count < n { 78 | storage = storage.cloned(capacity: n) 79 | } 80 | } 81 | 82 | public mutating func replaceSubrange(_ subrange: Range, with newElements: C) 83 | where C : Collection, Element == C.Element 84 | { 85 | _makeUnique() 86 | let newCount = (count - subrange.count) + newElements.count 87 | if newCount > storage.capacity { 88 | storage = storage.cloned(capacity: newCount) 89 | } 90 | 91 | let startOfSubrange = storage.buffer.baseAddress! + subrange.lowerBound 92 | let endOfSubrange = startOfSubrange + subrange.count 93 | let endOfNewElements = startOfSubrange + newElements.count 94 | let countAfterSubrange = count - subrange.upperBound 95 | 96 | // clear out old elements 97 | startOfSubrange.deinitialize(count: subrange.count) 98 | 99 | // move elements above subrange 100 | endOfNewElements.moveInitialize(from: endOfSubrange, count: countAfterSubrange) 101 | 102 | // assign new elements 103 | for (pointer, element) in zip(startOfSubrange..., newElements) { 104 | pointer.initialize(to: element) 105 | } 106 | 107 | // update count 108 | storage.count = newCount 109 | } 110 | 111 | public init(arrayLiteral elements: Element...) { 112 | storage = Storage(capacity: elements.count) 113 | replaceSubrange(0..<0, with: elements) 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /Sources/TestHelpers/XCTestExtensions.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | import XCTest 13 | 14 | public func XCTAssertEqual(_ collection1: C1, _ collection2: C2, file: StaticString = #file, line: UInt = #line) 15 | where C1: Collection, C2: Collection, C1.Element == C2.Element, C1.Element: Equatable 16 | { 17 | for (i, (e1, e2)) in zip(0..., zip(collection1, collection2)) { 18 | XCTAssertEqual(e1, e2, "Elements differ at position \(i): '\(e1)' is not equal to '\(e2)'", file: file, line: line) 19 | } 20 | XCTAssert(collection1.count == collection2.count, "Collections have different lengths", file: file, line: line) 21 | } 22 | -------------------------------------------------------------------------------- /Tests/SE0270_RangeSet_Tests/CollectionExtensionsTests.swift: -------------------------------------------------------------------------------- 1 | //===----------------------------------------------------------*- swift -*-===// 2 | // 3 | // This source file is part of the Swift open source project 4 | // 5 | // Copyright (c) 2020 Apple Inc. and the Swift project authors 6 | // Licensed under Apache License v2.0 with Runtime Library Exception 7 | // 8 | // See https://swift.org/LICENSE.txt for license information 9 | // 10 | //===----------------------------------------------------------------------===// 11 | 12 | import XCTest 13 | import SE0270_RangeSet 14 | import TestHelpers 15 | 16 | let letterString = "ABCdefGHIjklMNOpqrStUvWxyz" 17 | let lowercaseLetters = letterString.filter { $0.isLowercase } 18 | let uppercaseLetters = letterString.filter { $0.isUppercase } 19 | 20 | extension Collection { 21 | func every(_ n: Int) -> [Element] { 22 | sequence(first: startIndex) { i in 23 | let next = self.index(i, offsetBy: n, limitedBy: self.endIndex) 24 | return next == self.endIndex ? nil : next 25 | }.map { self[$0] } 26 | } 27 | } 28 | 29 | final class CollectionExtensionsTests: XCTestCase { 30 | func testIndicesWhere() { 31 | let a = [1, 2, 3, 4, 3, 3, 4, 5, 3, 4, 3, 3, 3] 32 | let indices = a.subranges(of: 3) 33 | XCTAssertEqual(indices, RangeSet([2..<3, 4..<6, 8..<9, 10..<13])) 34 | 35 | let allTheThrees = a[indices] 36 | XCTAssertEqual(allTheThrees.count, 7) 37 | XCTAssertTrue(allTheThrees.allSatisfy { $0 == 3 }) 38 | XCTAssertEqual(allTheThrees, repeatElement(3, count: 7)) 39 | 40 | let lowerIndices = letterString.subranges(where: { $0.isLowercase }) 41 | let lowerOnly = letterString[lowerIndices] 42 | XCTAssertEqual(lowerOnly, lowercaseLetters) 43 | XCTAssertEqual(lowerOnly.reversed(), lowercaseLetters.reversed()) 44 | 45 | let upperOnly = letterString.removingSubranges(lowerIndices) 46 | XCTAssertEqual(upperOnly, uppercaseLetters) 47 | XCTAssertEqual(upperOnly.reversed(), uppercaseLetters.reversed()) 48 | } 49 | 50 | func testRemoveAllRangeSet() { 51 | var a = [1, 2, 3, 4, 3, 3, 4, 5, 3, 4, 3, 3, 3] 52 | let indices = a.subranges(of: 3) 53 | a.removeSubranges(indices) 54 | XCTAssertEqual(a, [1, 2, 4, 4, 5, 4]) 55 | 56 | var numbers = Array(1...20) 57 | numbers.removeSubranges(RangeSet([2..<5, 10..<15, 18..<20])) 58 | XCTAssertEqual(numbers, [1, 2, 6, 7, 8, 9, 10, 16, 17, 18]) 59 | 60 | numbers = Array(1...20) 61 | numbers.removeSubranges(.init()) 62 | XCTAssertEqual(Array(1...20), numbers) 63 | 64 | let sameNumbers = numbers.removingSubranges(.init()) 65 | XCTAssertEqual(numbers, sameNumbers) 66 | 67 | var str = letterString 68 | let lowerIndices = str.subranges(where: { $0.isLowercase }) 69 | 70 | let upperOnly = str.removingSubranges(lowerIndices) 71 | XCTAssertEqual(upperOnly, uppercaseLetters) 72 | 73 | str.removeSubranges(lowerIndices) 74 | XCTAssertEqual(str, uppercaseLetters) 75 | } 76 | 77 | func testGatherRangeSet() { 78 | // Move before 79 | var numbers = Array(1...20) 80 | let range1 = numbers.moveSubranges(RangeSet([10..<15, 18..<20]), to: 4) 81 | XCTAssertEqual(range1, 4..<11) 82 | XCTAssertEqual(numbers, [ 83 | 1, 2, 3, 4, 84 | 11, 12, 13, 14, 15, 85 | 19, 20, 86 | 5, 6, 7, 8, 9, 10, 16, 17, 18]) 87 | 88 | // Move to start 89 | numbers = Array(1...20) 90 | let range2 = numbers.moveSubranges(RangeSet([10..<15, 18..<20]), to: 0) 91 | XCTAssertEqual(range2, 0..<7) 92 | XCTAssertEqual(numbers, [ 93 | 11, 12, 13, 14, 15, 94 | 19, 20, 95 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17, 18]) 96 | 97 | // Move to end 98 | numbers = Array(1...20) 99 | let range3 = numbers.moveSubranges(RangeSet([10..<15, 18..<20]), to: 20) 100 | XCTAssertEqual(range3, 13..<20) 101 | XCTAssertEqual(numbers, [ 102 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 17, 18, 103 | 11, 12, 13, 14, 15, 104 | 19, 20, 105 | ]) 106 | 107 | // Move to middle of selected elements 108 | numbers = Array(1...20) 109 | let range4 = numbers.moveSubranges(RangeSet([10..<15, 18..<20]), to: 14) 110 | XCTAssertEqual(range4, 10..<17) 111 | XCTAssertEqual(numbers, [ 112 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 113 | 11, 12, 13, 14, 15, 114 | 19, 20, 115 | 16, 17, 18]) 116 | 117 | // Move none 118 | numbers = Array(1...20) 119 | let range5 = numbers.moveSubranges(RangeSet(), to: 10) 120 | XCTAssertEqual(range5, 10..<10) 121 | XCTAssertEqual(numbers, Array(1...20)) 122 | } 123 | 124 | func testDiscontiguousSliceSlicing() { 125 | let initial = 1...100 126 | 127 | // Build an array of ranges that include alternating groups of 5 elements 128 | // e.g. 1...5, 11...15, etc 129 | let rangeStarts = initial.indices.every(10) 130 | let rangeEnds = rangeStarts.compactMap { 131 | initial.index($0, offsetBy: 5, limitedBy: initial.endIndex) 132 | } 133 | let ranges = zip(rangeStarts, rangeEnds).map(Range.init) 134 | 135 | // Create a collection of the elements represented by `ranges` without 136 | // using `RangeSet` 137 | let chosenElements = ranges.map { initial[$0] }.joined() 138 | 139 | let set = RangeSet(ranges) 140 | let discontiguousSlice = initial[set] 141 | XCTAssertEqual(discontiguousSlice, chosenElements) 142 | 143 | for (chosenIdx, disIdx) in zip(chosenElements.indices, discontiguousSlice.indices) { 144 | XCTAssertEqual(chosenElements[chosenIdx...], discontiguousSlice[disIdx...]) 145 | XCTAssertEqual(chosenElements[.. RangeSet { 20 | var set = RangeSet() 21 | for _ in 0..<100 { 22 | var (a, b) = (Int.random(in: -100...100), Int.random(in: -100...100)) 23 | if (a > b) { swap(&a, &b) } 24 | if Double.random(in: 0..<1) > 0.3 { 25 | set.insert(contentsOf: a.., _ s2: RangeSet) -> RangeSet { 158 | let set1 = Set(parent.indices[s1]) 159 | let set2 = Set(parent.indices[s2]) 160 | return RangeSet(set1.intersection(set2), within: .min ..< .max) 161 | } 162 | 163 | do { 164 | // Simple test 165 | let set1 = RangeSet([0..<5, 9..<14]) 166 | let set2 = RangeSet([1..<3, 4..<6, 8..<12]) 167 | let intersection = RangeSet([1..<3, 4..<5, 9..<12]) 168 | XCTAssertEqual(set1.intersection(set2), intersection) 169 | XCTAssertEqual(set2.intersection(set1), intersection) 170 | } 171 | 172 | do { 173 | // Test with upper bound / lower bound equality 174 | let set1 = RangeSet([10..<20, 30..<40]) 175 | let set2 = RangeSet([15..<30, 40..<50]) 176 | let intersection = RangeSet([15..<20]) 177 | XCTAssertEqual(set1.intersection(set2), intersection) 178 | XCTAssertEqual(set2.intersection(set1), intersection) 179 | } 180 | 181 | for _ in 0..<100 { 182 | let set1 = buildRandomRangeSet() 183 | let set2 = buildRandomRangeSet() 184 | 185 | let rangeSetIntersection = set1.intersection(set2) 186 | let stdlibSetIntersection = intersectionViaSet(set1, set2) 187 | XCTAssertEqual(rangeSetIntersection, stdlibSetIntersection) 188 | } 189 | } 190 | 191 | func testSymmetricDifference() { 192 | func symmetricDifferenceViaSet(_ s1: RangeSet, _ s2: RangeSet) -> RangeSet { 193 | let set1 = Set(parent.indices[s1]) 194 | let set2 = Set(parent.indices[s2]) 195 | return RangeSet(set1.symmetricDifference(set2), within: .min ..< .max) 196 | } 197 | 198 | do { 199 | // Simple test 200 | let set1 = RangeSet([0..<5, 9..<14]) 201 | let set2 = RangeSet([1..<3, 4..<6, 8..<12]) 202 | let difference = RangeSet([0..<1, 3..<4, 5..<6, 8..<9, 12..<14]) 203 | XCTAssertEqual(set1.symmetricDifference(set2), difference) 204 | XCTAssertEqual(set2.symmetricDifference(set1), difference) 205 | } 206 | 207 | do { 208 | // Test with upper bound / lower bound equality 209 | let set1 = RangeSet([10..<20, 30..<40]) 210 | let set2 = RangeSet([15..<30, 40..<50]) 211 | let difference = RangeSet([10..<15, 20..<50]) 212 | XCTAssertEqual(set1.symmetricDifference(set2), difference) 213 | XCTAssertEqual(set2.symmetricDifference(set1), difference) 214 | } 215 | 216 | for _ in 0..<100 { 217 | let set1 = buildRandomRangeSet() 218 | let set2 = buildRandomRangeSet() 219 | 220 | let rangeSetDifference = set1.symmetricDifference(set2) 221 | let stdlibSetDifference = symmetricDifferenceViaSet(set1, set2) 222 | XCTAssertEqual(rangeSetDifference, stdlibSetDifference) 223 | } 224 | } 225 | } 226 | --------------------------------------------------------------------------------