99//
1010//===----------------------------------------------------------------------===//
1111
12- public struct LazyChunked < Base: Collection > {
12+ public struct LazyChunked < Base: Collection , Subject > {
1313 /// The collection that this instance provides a view onto.
1414 public let base : Base
1515
1616 /// The projection function.
1717 @usableFromInline
18- internal var belongInSameGroup : ( Base . Element , Base . Element ) -> Bool
18+ internal let projection : ( Base . Element ) -> Subject
1919
20+ /// The predicate.
2021 @usableFromInline
21- internal init ( base: Base , belongInSameGroup: @escaping ( Base . Element , Base . Element ) -> Bool ) {
22+ internal let belongInSameGroup : ( Subject , Subject ) -> Bool
23+
24+ @usableFromInline
25+ internal init (
26+ base: Base ,
27+ projection: @escaping ( Base . Element ) -> Subject ,
28+ belongInSameGroup: @escaping ( Subject , Subject ) -> Bool
29+ ) {
2230 self . base = base
31+ self . projection = projection
2332 self . belongInSameGroup = belongInSameGroup
2433 }
2534}
@@ -64,13 +73,14 @@ extension LazyChunked: LazyCollectionProtocol {
6473 }
6574 }
6675
67- /// Returns the index in the base collection for the first element that
68- /// doesn't match the current chunk .
76+ /// Returns the index in the base collection of the end of the chunk starting
77+ /// at the given index .
6978 @usableFromInline
70- internal func endOfChunk( from i: Base . Index ) -> Base . Index {
71- guard i != base. endIndex else { return base. endIndex }
72- return base [ base. index ( after: i) ... ]
73- . firstIndex ( where: { !belongInSameGroup( $0, base [ i] ) } ) ?? base. endIndex
79+ internal func endOfChunk( startingAt start: Base . Index ) -> Base . Index {
80+ let subject = projection ( base [ start] )
81+ return base [ base. index ( after: start) ... ]
82+ . firstIndex ( where: { !belongInSameGroup( subject, projection ( $0) ) } )
83+ ?? base. endIndex
7484 }
7585
7686 @inlinable
@@ -80,20 +90,22 @@ extension LazyChunked: LazyCollectionProtocol {
8090
8191 @inlinable
8292 public var endIndex : Index {
83- Index ( lowerBound: base. endIndex, upperBound : base . endIndex )
93+ Index ( lowerBound: base. endIndex)
8494 }
8595
8696 @inlinable
8797 public func index( after i: Index ) -> Index {
88- let upperBound = i. upperBound ?? endOfChunk ( from: i. lowerBound)
89- let end = endOfChunk ( from: upperBound)
98+ precondition ( i != endIndex, " Can't advance past endIndex " )
99+ let upperBound = i. upperBound ?? endOfChunk ( startingAt: i. lowerBound)
100+ guard upperBound != base. endIndex else { return endIndex }
101+ let end = endOfChunk ( startingAt: upperBound)
90102 return Index ( lowerBound: upperBound, upperBound: end)
91103 }
92104
93105 @inlinable
94106 public subscript( position: Index ) -> Base . SubSequence {
95107 let upperBound = position. upperBound
96- ?? endOfChunk ( from : position. lowerBound)
108+ ?? endOfChunk ( startingAt : position. lowerBound)
97109 return base [ position. lowerBound..< upperBound]
98110 }
99111}
@@ -103,17 +115,19 @@ extension LazyChunked.Index: Hashable where Base.Index: Hashable {}
103115extension LazyChunked : BidirectionalCollection
104116 where Base: BidirectionalCollection
105117{
106- /// Returns the index in the base collection for the element that starts
107- /// the chunk ending at the given index.
118+ /// Returns the index in the base collection of the start of the chunk ending
119+ /// at the given index.
108120 @usableFromInline
109121 internal func startOfChunk( endingAt end: Base . Index ) -> Base . Index {
122+ let indexBeforeEnd = base. index ( before: end)
123+
110124 // Get the projected value of the last element in the range ending at `end`.
111- let lastOfPreviousChunk = base [ base . index ( before : end ) ]
125+ let subject = projection ( base [ indexBeforeEnd ] )
112126
113127 // Search backward from `end` for the first element whose projection isn't
114- // equal to `lastOfPreviousChunk `.
115- if let firstMismatch = base [ ..< end ]
116- . lastIndex ( where: { !belongInSameGroup( $0 , lastOfPreviousChunk ) } )
128+ // equal to `subject `.
129+ if let firstMismatch = base [ ..< indexBeforeEnd ]
130+ . lastIndex ( where: { !belongInSameGroup( projection ( $0 ) , subject ) } )
117131 {
118132 // If we found one, that's the last element of the _next_ previous chunk,
119133 // and therefore one position _before_ the start of this chunk.
@@ -127,6 +141,7 @@ extension LazyChunked: BidirectionalCollection
127141
128142 @inlinable
129143 public func index( before i: Index ) -> Index {
144+ precondition ( i != startIndex, " Can't advance before startIndex " )
130145 let start = startOfChunk ( endingAt: i. lowerBound)
131146 return Index ( lowerBound: start, upperBound: i. lowerBound)
132147 }
@@ -146,8 +161,11 @@ extension LazyCollectionProtocol {
146161 @inlinable
147162 public func chunked(
148163 by belongInSameGroup: @escaping ( Element , Element ) -> Bool
149- ) -> LazyChunked < Elements > {
150- LazyChunked ( base: elements, belongInSameGroup: belongInSameGroup)
164+ ) -> LazyChunked < Elements , Element > {
165+ LazyChunked (
166+ base: elements,
167+ projection: { $0 } ,
168+ belongInSameGroup: belongInSameGroup)
151169 }
152170
153171 /// Returns a lazy collection of subsequences of this collection, chunked by
@@ -159,10 +177,11 @@ extension LazyCollectionProtocol {
159177 @inlinable
160178 public func chunked< Subject: Equatable > (
161179 on projection: @escaping ( Element ) -> Subject
162- ) -> LazyChunked < Elements > {
180+ ) -> LazyChunked < Elements , Subject > {
163181 LazyChunked (
164182 base: elements,
165- belongInSameGroup: { projection ( $0) == projection ( $1) } )
183+ projection: projection,
184+ belongInSameGroup: == )
166185 }
167186}
168187
@@ -172,27 +191,28 @@ extension LazyCollectionProtocol {
172191
173192extension Collection {
174193 /// Returns a collection of subsequences of this collection, chunked by
175- /// the given predicate.
194+ /// grouping elements that project to the same value according to the given
195+ /// predicate.
176196 ///
177197 /// - Complexity: O(*n*), where *n* is the length of this collection.
178- @inlinable
179- public func chunked(
180- by belongInSameGroup: ( Element , Element ) -> Bool
181- ) -> [ SubSequence ] {
198+ @usableFromInline
199+ internal func chunked< Subject> (
200+ on projection: ( Element ) throws -> Subject ,
201+ by belongInSameGroup: ( Subject , Subject ) throws -> Bool
202+ ) rethrows -> [ SubSequence ] {
182203 guard !isEmpty else { return [ ] }
183204 var result : [ SubSequence ] = [ ]
184205
185206 var start = startIndex
186- var current = startIndex
187- while true {
188- let next = index ( after : current )
189- if next == endIndex { break }
190-
191- if !belongInSameGroup ( self [ current ] , self [ next ] ) {
192- result . append ( self [ start..< next ] )
193- start = next
207+ var subject = try projection ( self [ start ] )
208+
209+ for (index , element ) in indexed ( ) . dropFirst ( ) {
210+ let nextSubject = try projection ( element )
211+ if try ! belongInSameGroup ( subject , nextSubject ) {
212+ result . append ( self [ start ..< index ] )
213+ start = index
214+ subject = nextSubject
194215 }
195- current = next
196216 }
197217
198218 if start != endIndex {
@@ -201,16 +221,26 @@ extension Collection {
201221
202222 return result
203223 }
224+
225+ /// Returns a collection of subsequences of this collection, chunked by
226+ /// the given predicate.
227+ ///
228+ /// - Complexity: O(*n*), where *n* is the length of this collection.
229+ @inlinable
230+ public func chunked(
231+ by belongInSameGroup: ( Element , Element ) throws -> Bool
232+ ) rethrows -> [ SubSequence ] {
233+ try chunked ( on: { $0 } , by: belongInSameGroup)
234+ }
204235
205236 /// Returns a collection of subsequences of this collection, chunked by
206237 /// grouping elements that project to the same value.
207238 ///
208239 /// - Complexity: O(*n*), where *n* is the length of this collection.
209240 @inlinable
210241 public func chunked< Subject: Equatable > (
211- on projection: ( Element ) -> Subject
212- ) -> [ SubSequence ] {
213- chunked ( by : { projection ( $0 ) == projection ( $1 ) } )
242+ on projection: ( Element ) throws -> Subject
243+ ) rethrows -> [ SubSequence ] {
244+ try chunked ( on : projection, by : == )
214245 }
215246}
216-
0 commit comments