Chunked.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Algorithms 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. /// A collection wrapper that breaks a collection into chunks based on a
  12. /// predicate.
  13. ///
  14. /// Call `lazy.chunked(by:)` on a collection to create an instance of this type.
  15. public struct ChunkedByCollection<Base: Collection, Subject> {
  16. /// The collection that this instance provides a view onto.
  17. @usableFromInline
  18. internal let base: Base
  19. /// The projection function.
  20. @usableFromInline
  21. internal let projection: (Base.Element) -> Subject
  22. /// The predicate.
  23. @usableFromInline
  24. internal let belongInSameGroup: (Subject, Subject) -> Bool
  25. /// The end index of the first chunk.
  26. @usableFromInline
  27. internal var endOfFirstChunk: Base.Index
  28. @inlinable
  29. internal init(
  30. base: Base,
  31. projection: @escaping (Base.Element) -> Subject,
  32. belongInSameGroup: @escaping (Subject, Subject) -> Bool
  33. ) {
  34. self.base = base
  35. self.projection = projection
  36. self.belongInSameGroup = belongInSameGroup
  37. self.endOfFirstChunk = base.startIndex
  38. if !base.isEmpty {
  39. endOfFirstChunk = endOfChunk(startingAt: base.startIndex)
  40. }
  41. }
  42. }
  43. extension ChunkedByCollection: Collection {
  44. /// A position in a chunked collection.
  45. public struct Index: Comparable {
  46. /// The range corresponding to the chunk at this position.
  47. @usableFromInline
  48. internal var baseRange: Range<Base.Index>
  49. @inlinable
  50. internal init(_ baseRange: Range<Base.Index>) {
  51. self.baseRange = baseRange
  52. }
  53. @inlinable
  54. public static func == (lhs: Index, rhs: Index) -> Bool {
  55. // Since each index represents the range of a disparate chunk, no two
  56. // unique indices will have the same lower bound.
  57. lhs.baseRange.lowerBound == rhs.baseRange.lowerBound
  58. }
  59. @inlinable
  60. public static func < (lhs: Index, rhs: Index) -> Bool {
  61. // Only use the lower bound to test for ordering, as above.
  62. lhs.baseRange.lowerBound < rhs.baseRange.lowerBound
  63. }
  64. }
  65. /// Returns the index in the base collection of the end of the chunk starting
  66. /// at the given index.
  67. @inlinable
  68. internal func endOfChunk(startingAt start: Base.Index) -> Base.Index {
  69. var subject = projection(base[start])
  70. return base[base.index(after: start)...].endOfPrefix(while: { element in
  71. let nextSubject = projection(element)
  72. defer { subject = nextSubject }
  73. return belongInSameGroup(subject, nextSubject)
  74. })
  75. }
  76. @inlinable
  77. public var startIndex: Index {
  78. Index(base.startIndex..<endOfFirstChunk)
  79. }
  80. @inlinable
  81. public var endIndex: Index {
  82. Index(base.endIndex..<base.endIndex)
  83. }
  84. @inlinable
  85. public func index(after i: Index) -> Index {
  86. precondition(i != endIndex, "Can't advance past endIndex")
  87. let upperBound = i.baseRange.upperBound
  88. guard upperBound != base.endIndex else { return endIndex }
  89. let end = endOfChunk(startingAt: upperBound)
  90. return Index(upperBound..<end)
  91. }
  92. @inlinable
  93. public subscript(position: Index) -> Base.SubSequence {
  94. precondition(position != endIndex, "Can't subscript using endIndex")
  95. return base[position.baseRange]
  96. }
  97. }
  98. extension ChunkedByCollection.Index: Hashable where Base.Index: Hashable {}
  99. extension ChunkedByCollection: BidirectionalCollection
  100. where Base: BidirectionalCollection
  101. {
  102. /// Returns the index in the base collection of the start of the chunk ending
  103. /// at the given index.
  104. @inlinable
  105. internal func startOfChunk(endingAt end: Base.Index) -> Base.Index {
  106. let indexBeforeEnd = base.index(before: end)
  107. var subject = projection(base[indexBeforeEnd])
  108. return base[..<indexBeforeEnd].startOfSuffix(while: { element in
  109. let nextSubject = projection(element)
  110. defer { subject = nextSubject }
  111. return belongInSameGroup(nextSubject, subject)
  112. })
  113. }
  114. @inlinable
  115. public func index(before i: Index) -> Index {
  116. precondition(i != startIndex, "Can't advance before startIndex")
  117. let start = startOfChunk(endingAt: i.baseRange.lowerBound)
  118. return Index(start..<i.baseRange.lowerBound)
  119. }
  120. }
  121. extension ChunkedByCollection: LazyCollectionProtocol {}
  122. /// A collection wrapper that breaks a collection into chunks based on a
  123. /// predicate.
  124. ///
  125. /// Call `lazy.chunked(on:)` on a collection to create an instance of this type.
  126. public struct ChunkedOnCollection<Base: Collection, Subject: Equatable> {
  127. @usableFromInline
  128. internal var chunked: ChunkedByCollection<Base, Subject>
  129. @inlinable
  130. internal init(
  131. base: Base,
  132. projection: @escaping (Base.Element) -> Subject
  133. ) {
  134. self.chunked = ChunkedByCollection(
  135. base: base,
  136. projection: projection,
  137. belongInSameGroup: ==)
  138. }
  139. }
  140. extension ChunkedOnCollection: Collection {
  141. public typealias Index = ChunkedByCollection<Base, Subject>.Index
  142. @inlinable
  143. public var startIndex: Index {
  144. chunked.startIndex
  145. }
  146. @inlinable
  147. public var endIndex: Index {
  148. chunked.endIndex
  149. }
  150. @inlinable
  151. public subscript(position: Index) -> (Subject, Base.SubSequence) {
  152. let subsequence = chunked[position]
  153. let subject = chunked.projection(subsequence.first!)
  154. return (subject, subsequence)
  155. }
  156. @inlinable
  157. public func index(after i: Index) -> Index {
  158. chunked.index(after: i)
  159. }
  160. }
  161. extension ChunkedOnCollection: BidirectionalCollection
  162. where Base: BidirectionalCollection
  163. {
  164. @inlinable
  165. public func index(before i: Index) -> Index {
  166. chunked.index(before: i)
  167. }
  168. }
  169. extension ChunkedOnCollection: LazyCollectionProtocol {}
  170. //===----------------------------------------------------------------------===//
  171. // lazy.chunked(by:) / lazy.chunked(on:)
  172. //===----------------------------------------------------------------------===//
  173. extension LazySequenceProtocol where Self: Collection, Elements: Collection {
  174. /// Returns a lazy collection of subsequences of this collection, chunked by
  175. /// the given predicate.
  176. ///
  177. /// - Complexity: O(*n*), because the start index is pre-computed.
  178. @inlinable
  179. public func chunked(
  180. by belongInSameGroup: @escaping (Element, Element) -> Bool
  181. ) -> ChunkedByCollection<Elements, Element> {
  182. ChunkedByCollection(
  183. base: elements,
  184. projection: { $0 },
  185. belongInSameGroup: belongInSameGroup)
  186. }
  187. /// Returns a lazy collection of subsequences of this collection, chunked by
  188. /// grouping elements that project to the same value.
  189. ///
  190. /// - Complexity: O(*n*), because the start index is pre-computed.
  191. @inlinable
  192. public func chunked<Subject>(
  193. on projection: @escaping (Element) -> Subject
  194. ) -> ChunkedOnCollection<Elements, Subject> {
  195. ChunkedOnCollection(
  196. base: elements,
  197. projection: projection)
  198. }
  199. }
  200. //===----------------------------------------------------------------------===//
  201. // chunked(by:) / chunked(on:)
  202. //===----------------------------------------------------------------------===//
  203. extension Collection {
  204. /// Returns a collection of subsequences of this collection, chunked by the
  205. /// given predicate.
  206. ///
  207. /// - Complexity: O(*n*), where *n* is the length of this collection.
  208. @inlinable
  209. public func chunked(
  210. by belongInSameGroup: (Element, Element) throws -> Bool
  211. ) rethrows -> [SubSequence] {
  212. guard !isEmpty else { return [] }
  213. var result: [SubSequence] = []
  214. var start = startIndex
  215. var current = self[start]
  216. for (index, element) in indexed().dropFirst() {
  217. if try !belongInSameGroup(current, element) {
  218. result.append(self[start..<index])
  219. start = index
  220. }
  221. current = element
  222. }
  223. if start != endIndex {
  224. result.append(self[start...])
  225. }
  226. return result
  227. }
  228. /// Returns a collection of subsequences of this collection, chunked by
  229. /// grouping elements that project to the same value.
  230. ///
  231. /// - Complexity: O(*n*), where *n* is the length of this collection.
  232. @inlinable
  233. public func chunked<Subject: Equatable>(
  234. on projection: (Element) throws -> Subject
  235. ) rethrows -> [(Subject, SubSequence)] {
  236. guard !isEmpty else { return [] }
  237. var result: [(Subject, SubSequence)] = []
  238. var start = startIndex
  239. var subject = try projection(self[start])
  240. for (index, element) in indexed().dropFirst() {
  241. let nextSubject = try projection(element)
  242. if subject != nextSubject {
  243. result.append((subject, self[start..<index]))
  244. start = index
  245. subject = nextSubject
  246. }
  247. }
  248. if start != endIndex {
  249. result.append((subject, self[start...]))
  250. }
  251. return result
  252. }
  253. }
  254. //===----------------------------------------------------------------------===//
  255. // chunks(ofCount:)
  256. //===----------------------------------------------------------------------===//
  257. /// A collection that presents the elements of its base collection in
  258. /// `SubSequence` chunks of any given count.
  259. ///
  260. /// A `ChunksOfCountCollection` is a lazy view on the base Collection, but it
  261. /// does not implicitly confer laziness on algorithms applied to its result. In
  262. /// other words, for ordinary collections `c`:
  263. ///
  264. /// * `c.chunks(ofCount: 3)` does not create new storage
  265. /// * `c.chunks(ofCount: 3).map(f)` maps eagerly and returns a new array
  266. /// * `c.lazy.chunks(ofCount: 3).map(f)` maps lazily and returns a
  267. /// `LazyMapCollection`
  268. public struct ChunksOfCountCollection<Base: Collection> {
  269. public typealias Element = Base.SubSequence
  270. @usableFromInline
  271. internal let base: Base
  272. @usableFromInline
  273. internal let chunkCount: Int
  274. @usableFromInline
  275. internal var endOfFirstChunk: Base.Index
  276. /// Creates a view instance that presents the elements of `base` in
  277. /// `SubSequence` chunks of the given count.
  278. ///
  279. /// - Complexity: O(*n*), because the start index is pre-computed.
  280. @inlinable
  281. internal init(_base: Base, _chunkCount: Int) {
  282. self.base = _base
  283. self.chunkCount = _chunkCount
  284. // Compute the start index upfront in order to make start index a O(1)
  285. // lookup.
  286. self.endOfFirstChunk = _base.index(
  287. _base.startIndex, offsetBy: _chunkCount,
  288. limitedBy: _base.endIndex
  289. ) ?? _base.endIndex
  290. }
  291. }
  292. extension ChunksOfCountCollection: Collection {
  293. public struct Index {
  294. @usableFromInline
  295. internal let baseRange: Range<Base.Index>
  296. @inlinable
  297. internal init(_baseRange: Range<Base.Index>) {
  298. self.baseRange = _baseRange
  299. }
  300. }
  301. /// - Complexity: O(1)
  302. @inlinable
  303. public var startIndex: Index {
  304. Index(_baseRange: base.startIndex..<endOfFirstChunk)
  305. }
  306. @inlinable
  307. public var endIndex: Index {
  308. Index(_baseRange: base.endIndex..<base.endIndex)
  309. }
  310. /// - Complexity: O(1)
  311. @inlinable
  312. public subscript(i: Index) -> Element {
  313. precondition(i != endIndex, "Index out of range")
  314. return base[i.baseRange]
  315. }
  316. @inlinable
  317. public func index(after i: Index) -> Index {
  318. precondition(i != endIndex, "Advancing past end index")
  319. let baseIdx = base.index(
  320. i.baseRange.upperBound, offsetBy: chunkCount,
  321. limitedBy: base.endIndex
  322. ) ?? base.endIndex
  323. return Index(_baseRange: i.baseRange.upperBound..<baseIdx)
  324. }
  325. }
  326. extension ChunksOfCountCollection.Index: Comparable {
  327. @inlinable
  328. public static func == (lhs: ChunksOfCountCollection.Index,
  329. rhs: ChunksOfCountCollection.Index) -> Bool {
  330. lhs.baseRange.lowerBound == rhs.baseRange.lowerBound
  331. }
  332. @inlinable
  333. public static func < (lhs: ChunksOfCountCollection.Index,
  334. rhs: ChunksOfCountCollection.Index) -> Bool {
  335. lhs.baseRange.lowerBound < rhs.baseRange.lowerBound
  336. }
  337. }
  338. extension ChunksOfCountCollection:
  339. BidirectionalCollection, RandomAccessCollection
  340. where Base: RandomAccessCollection {
  341. @inlinable
  342. public func index(before i: Index) -> Index {
  343. precondition(i != startIndex, "Advancing past start index")
  344. var offset = chunkCount
  345. if i.baseRange.lowerBound == base.endIndex {
  346. let remainder = base.count % chunkCount
  347. if remainder != 0 {
  348. offset = remainder
  349. }
  350. }
  351. let baseIdx = base.index(
  352. i.baseRange.lowerBound, offsetBy: -offset,
  353. limitedBy: base.startIndex
  354. ) ?? base.startIndex
  355. return Index(_baseRange: baseIdx..<i.baseRange.lowerBound)
  356. }
  357. }
  358. extension ChunksOfCountCollection {
  359. @inlinable
  360. public func distance(from start: Index, to end: Index) -> Int {
  361. let distance =
  362. base.distance(from: start.baseRange.lowerBound,
  363. to: end.baseRange.lowerBound)
  364. let (quotient, remainder) =
  365. distance.quotientAndRemainder(dividingBy: chunkCount)
  366. return quotient + remainder.signum()
  367. }
  368. @inlinable
  369. public var count: Int {
  370. let (quotient, remainder) =
  371. base.count.quotientAndRemainder(dividingBy: chunkCount)
  372. return quotient + remainder.signum()
  373. }
  374. @inlinable
  375. public func index(
  376. _ i: Index, offsetBy offset: Int, limitedBy limit: Index
  377. ) -> Index? {
  378. guard offset != 0 else { return i }
  379. guard limit != i else { return nil }
  380. if offset > 0 {
  381. return limit > i
  382. ? offsetForward(i, offsetBy: offset, limit: limit)
  383. : offsetForward(i, offsetBy: offset)
  384. } else {
  385. return limit < i
  386. ? offsetBackward(i, offsetBy: offset, limit: limit)
  387. : offsetBackward(i, offsetBy: offset)
  388. }
  389. }
  390. @inlinable
  391. public func index(_ i: Index, offsetBy distance: Int) -> Index {
  392. guard distance != 0 else { return i }
  393. let idx = distance > 0
  394. ? offsetForward(i, offsetBy: distance)
  395. : offsetBackward(i, offsetBy: distance)
  396. guard let index = idx else {
  397. fatalError("Out of bounds")
  398. }
  399. return index
  400. }
  401. @inlinable
  402. internal func offsetForward(
  403. _ i: Index, offsetBy distance: Int, limit: Index? = nil
  404. ) -> Index? {
  405. assert(distance > 0)
  406. return makeOffsetIndex(
  407. from: i, baseBound: base.endIndex,
  408. distance: distance, baseDistance: distance * chunkCount,
  409. limit: limit, by: >
  410. )
  411. }
  412. // Convenience to compute offset backward base distance.
  413. @inlinable
  414. internal func computeOffsetBackwardBaseDistance(
  415. _ i: Index, _ distance: Int
  416. ) -> Int {
  417. if i == endIndex {
  418. let remainder = base.count%chunkCount
  419. // We have to take it into account when calculating offsets.
  420. if remainder != 0 {
  421. // Distance "minus" one(at this point distance is negative) because we
  422. // need to adjust for the last position that have a variadic(remainder)
  423. // number of elements.
  424. return ((distance + 1) * chunkCount) - remainder
  425. }
  426. }
  427. return distance * chunkCount
  428. }
  429. @inlinable
  430. internal func offsetBackward(
  431. _ i: Index, offsetBy distance: Int, limit: Index? = nil
  432. ) -> Index? {
  433. assert(distance < 0)
  434. let baseDistance =
  435. computeOffsetBackwardBaseDistance(i, distance)
  436. return makeOffsetIndex(
  437. from: i, baseBound: base.startIndex,
  438. distance: distance, baseDistance: baseDistance,
  439. limit: limit, by: <
  440. )
  441. }
  442. // Helper to compute index(offsetBy:) index.
  443. @inlinable
  444. internal func makeOffsetIndex(
  445. from i: Index, baseBound: Base.Index, distance: Int, baseDistance: Int,
  446. limit: Index?, by limitFn: (Base.Index, Base.Index) -> Bool
  447. ) -> Index? {
  448. let baseIdx = base.index(
  449. i.baseRange.lowerBound, offsetBy: baseDistance,
  450. limitedBy: baseBound
  451. )
  452. if let limit = limit {
  453. if baseIdx == nil {
  454. // If we past the bounds while advancing forward and the limit is the
  455. // `endIndex`, since the computation on base don't take into account the
  456. // remainder, we have to make sure that passing the bound was because of
  457. // the distance not just because of a remainder. Special casing is less
  458. // expensive than always use count(which could be O(n) for non-random
  459. // access collection base) to compute the base distance taking remainder
  460. // into account.
  461. if baseDistance > 0 && limit == endIndex {
  462. if self.distance(from: i, to: limit) < distance {
  463. return nil
  464. }
  465. } else {
  466. return nil
  467. }
  468. }
  469. // Checks for the limit.
  470. let baseStartIdx = baseIdx ?? baseBound
  471. if limitFn(baseStartIdx, limit.baseRange.lowerBound) {
  472. return nil
  473. }
  474. }
  475. let baseStartIdx = baseIdx ?? baseBound
  476. let baseEndIdx = base.index(
  477. baseStartIdx, offsetBy: chunkCount, limitedBy: base.endIndex
  478. ) ?? base.endIndex
  479. return Index(_baseRange: baseStartIdx..<baseEndIdx)
  480. }
  481. }
  482. extension Collection {
  483. /// Returns a `ChunksOfCountCollection<Self>` view presenting the elements in
  484. /// chunks with count of the given count parameter.
  485. ///
  486. /// - Parameter count: The size of the chunks. If the count parameter is
  487. /// evenly divided by the count of the base `Collection` all the chunks will
  488. /// have the count equals to size. Otherwise, the last chunk will contain
  489. /// the remaining elements.
  490. ///
  491. /// let c = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  492. /// print(c.chunks(ofCount: 5).map(Array.init))
  493. /// // [[1, 2, 3, 4, 5], [6, 7, 8, 9, 10]]
  494. ///
  495. /// print(c.chunks(ofCount: 3).map(Array.init))
  496. /// // [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
  497. ///
  498. /// - Complexity: O(*n*), because the start index is pre-computed.
  499. @inlinable
  500. public func chunks(ofCount count: Int) -> ChunksOfCountCollection<Self> {
  501. precondition(count > 0, "Cannot chunk with count <= 0!")
  502. return ChunksOfCountCollection(_base: self, _chunkCount: count)
  503. }
  504. }
  505. extension ChunksOfCountCollection.Index: Hashable where Base.Index: Hashable {}
  506. extension ChunksOfCountCollection: LazySequenceProtocol, LazyCollectionProtocol
  507. where Base: LazySequenceProtocol {}