|
2 | 2 | //
|
3 | 3 | // This source file is part of the Swift Algorithms open source project
|
4 | 4 | //
|
5 |
| -// Copyright (c) 2020 Apple Inc. and the Swift project authors |
| 5 | +// Copyright (c) 2020-2021 Apple Inc. and the Swift project authors |
6 | 6 | // Licensed under Apache License v2.0 with Runtime Library Exception
|
7 | 7 | //
|
8 | 8 | // See https://swift.org/LICENSE.txt for license information
|
@@ -204,3 +204,137 @@ extension Collection {
|
204 | 204 | }
|
205 | 205 | }
|
206 | 206 |
|
| 207 | +//===----------------------------------------------------------------------===// |
| 208 | +// partitioned(by:) |
| 209 | +//===----------------------------------------------------------------------===// |
| 210 | + |
| 211 | +extension Sequence { |
| 212 | + /// Returns two arrays containing, in order, the elements of the sequence that |
| 213 | + /// do and don’t satisfy the given predicate. |
| 214 | + /// |
| 215 | + /// In this example, `partitioned(by:)` is used to separate the input based on |
| 216 | + /// whether a name is shorter than five characters: |
| 217 | + /// |
| 218 | + /// let cast = ["Vivien", "Marlon", "Kim", "Karl"] |
| 219 | + /// let (longNames, shortNames) = cast.partitioned(by: { $0.count < 5 }) |
| 220 | + /// print(longNames) |
| 221 | + /// // Prints "["Vivien", "Marlon"]" |
| 222 | + /// print(shortNames) |
| 223 | + /// // Prints "["Kim", "Karl"]" |
| 224 | + /// |
| 225 | + /// - Parameter predicate: A closure that takes an element of the sequence as |
| 226 | + /// its argument and returns a Boolean value indicating whether the element |
| 227 | + /// should be included in the second returned array. Otherwise, the element |
| 228 | + /// will appear in the first returned array. |
| 229 | + /// |
| 230 | + /// - Returns: Two arrays with all of the elements of the receiver. The |
| 231 | + /// first array contains all the elements that `predicate` didn’t allow, and |
| 232 | + /// the second array contains all the elements that `predicate` allowed. |
| 233 | + /// |
| 234 | + /// - Complexity: O(*n*), where *n* is the length of the sequence. |
| 235 | + @inlinable |
| 236 | + public func partitioned( |
| 237 | + by predicate: (Element) throws -> Bool |
| 238 | + ) rethrows -> (falseElements: [Element], trueElements: [Element]) { |
| 239 | + var lhs = [Element]() |
| 240 | + var rhs = [Element]() |
| 241 | + |
| 242 | + for element in self { |
| 243 | + if try predicate(element) { |
| 244 | + rhs.append(element) |
| 245 | + } else { |
| 246 | + lhs.append(element) |
| 247 | + } |
| 248 | + } |
| 249 | + |
| 250 | + return (lhs, rhs) |
| 251 | + } |
| 252 | +} |
| 253 | + |
| 254 | +extension Collection { |
| 255 | + /// Returns two arrays containing, in order, the elements of the collection |
| 256 | + /// that do and don’t satisfy the given predicate. |
| 257 | + /// |
| 258 | + /// In this example, `partitioned(by:)` is used to separate the input based on |
| 259 | + /// whether a name is shorter than five characters. |
| 260 | + /// |
| 261 | + /// let cast = ["Vivien", "Marlon", "Kim", "Karl"] |
| 262 | + /// let (longNames, shortNames) = cast.partitioned(by: { $0.count < 5 }) |
| 263 | + /// print(longNames) |
| 264 | + /// // Prints "["Vivien", "Marlon"]" |
| 265 | + /// print(shortNames) |
| 266 | + /// // Prints "["Kim", "Karl"]" |
| 267 | + /// |
| 268 | + /// - Parameter predicate: A closure that takes an element of the collection |
| 269 | + /// as its argument and returns a Boolean value indicating whether the element |
| 270 | + /// should be included in the second returned array. Otherwise, the element |
| 271 | + /// will appear in the first returned array. |
| 272 | + /// |
| 273 | + /// - Returns: Two arrays with all of the elements of the receiver. The |
| 274 | + /// first array contains all the elements that `predicate` didn’t allow, and |
| 275 | + /// the second array contains all the elements that `predicate` allowed. |
| 276 | + /// |
| 277 | + /// - Complexity: O(*n*), where *n* is the length of the collection. |
| 278 | + @inlinable |
| 279 | + public func partitioned( |
| 280 | + by predicate: (Element) throws -> Bool |
| 281 | + ) rethrows -> (falseElements: [Element], trueElements: [Element]) { |
| 282 | + guard !self.isEmpty else { |
| 283 | + return ([], []) |
| 284 | + } |
| 285 | + |
| 286 | + // Since collections have known sizes, we can allocate one array of size |
| 287 | + // `self.count`, then insert items at the beginning or end of that contiguous |
| 288 | + // block. This way, we don’t have to do any dynamic array resizing. Since we |
| 289 | + // insert the right elements on the right side in reverse order, we need to |
| 290 | + // reverse them back to the original order at the end. |
| 291 | + |
| 292 | + let count = self.count |
| 293 | + |
| 294 | + // Inside of the `initializer` closure, we set what the actual mid-point is. |
| 295 | + // We will use this to partition the single array into two. |
| 296 | + var midPoint: Int = 0 |
| 297 | + |
| 298 | + let elements = try [Element]( |
| 299 | + unsafeUninitializedCapacity: count, |
| 300 | + initializingWith: { buffer, initializedCount in |
| 301 | + var lhs = buffer.baseAddress! |
| 302 | + var rhs = lhs + buffer.count |
| 303 | + do { |
| 304 | + for element in self { |
| 305 | + if try predicate(element) { |
| 306 | + rhs -= 1 |
| 307 | + rhs.initialize(to: element) |
| 308 | + } else { |
| 309 | + lhs.initialize(to: element) |
| 310 | + lhs += 1 |
| 311 | + } |
| 312 | + } |
| 313 | + |
| 314 | + precondition(lhs == rhs, """ |
| 315 | + Collection's `count` differed from the number of elements iterated. |
| 316 | + """ |
| 317 | + ) |
| 318 | + |
| 319 | + let rhsIndex = rhs - buffer.baseAddress! |
| 320 | + buffer[rhsIndex...].reverse() |
| 321 | + initializedCount = buffer.count |
| 322 | + |
| 323 | + midPoint = rhsIndex |
| 324 | + } catch { |
| 325 | + let lhsCount = lhs - buffer.baseAddress! |
| 326 | + let rhsCount = (buffer.baseAddress! + buffer.count) - rhs |
| 327 | + buffer.baseAddress!.deinitialize(count: lhsCount) |
| 328 | + rhs.deinitialize(count: rhsCount) |
| 329 | + throw error |
| 330 | + } |
| 331 | + }) |
| 332 | + |
| 333 | + let lhs = elements[..<midPoint] |
| 334 | + let rhs = elements[midPoint...] |
| 335 | + return ( |
| 336 | + Array(lhs), |
| 337 | + Array(rhs) |
| 338 | + ) |
| 339 | + } |
| 340 | +} |
0 commit comments