Skip to content

Commit f0e6c30

Browse files
committed
[stdlib][SE-45] Add prefix(while:) and drop(while:) to Sequence
Includes lazy implementation courtesy of Nate Cook
1 parent a33203f commit f0e6c30

14 files changed

+928
-7
lines changed

stdlib/private/StdlibCollectionUnittest/CheckCollectionType.swift.gyb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,9 @@ internal func _product<C1 : Collection, C2 : Collection>(
486486
return '''
487487
C : %(protocol)s,
488488
CollectionWithEquatableElement : %(protocol)s,
489+
CollectionWithEquatableElement.SubSequence : Collection,
490+
CollectionWithEquatableElement.SubSequence.Iterator.Element
491+
== CollectionWithEquatableElement.Iterator.Element,
489492
C.SubSequence : %(protocol)s,
490493
C.SubSequence.Iterator.Element == C.Iterator.Element,
491494
C.SubSequence.Index == C.Index,

stdlib/private/StdlibCollectionUnittest/CheckMutableCollectionType.swift.gyb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ extension TestSuite {
115115
C.Indices.Index == C.Index,
116116
C.Indices.SubSequence == C.Indices,
117117
CollectionWithEquatableElement.Iterator.Element : Equatable,
118+
CollectionWithEquatableElement.SubSequence : Collection,
119+
CollectionWithEquatableElement.SubSequence.Iterator.Element
120+
== CollectionWithEquatableElement.Iterator.Element,
118121
CollectionWithComparableElement.Iterator.Element : Comparable {
119122

120123
var testNamePrefix = testNamePrefix
@@ -705,6 +708,9 @@ self.test("\(testNamePrefix).partition/InvalidOrderings") {
705708
C.Indices.Index == C.Index,
706709
C.Indices.SubSequence == C.Indices,
707710
CollectionWithEquatableElement.Iterator.Element : Equatable,
711+
CollectionWithEquatableElement.SubSequence : Collection,
712+
CollectionWithEquatableElement.SubSequence.Iterator.Element
713+
== CollectionWithEquatableElement.Iterator.Element,
708714
CollectionWithComparableElement.Iterator.Element : Comparable {
709715

710716
var testNamePrefix = testNamePrefix
@@ -858,6 +864,9 @@ self.test("\(testNamePrefix).partition/DispatchesThrough_withUnsafeMutableBuffer
858864
C.Indices.Index == C.Index,
859865
C.Indices.SubSequence == C.Indices,
860866
CollectionWithEquatableElement.Iterator.Element : Equatable,
867+
CollectionWithEquatableElement.SubSequence : Collection,
868+
CollectionWithEquatableElement.SubSequence.Iterator.Element
869+
== CollectionWithEquatableElement.Iterator.Element,
861870
CollectionWithComparableElement.Iterator.Element : Comparable,
862871
CollectionWithComparableElement.SubSequence.Indices.Iterator.Element == CollectionWithComparableElement.Index {
863872

stdlib/private/StdlibCollectionUnittest/CheckRangeReplaceableCollectionType.swift

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,10 @@ extension TestSuite {
471471
C.Indices.Iterator.Element == C.Index,
472472
C.Indices.Index == C.Index,
473473
C.Indices.SubSequence == C.Indices,
474-
CollectionWithEquatableElement.Iterator.Element : Equatable {
474+
CollectionWithEquatableElement.Iterator.Element : Equatable,
475+
CollectionWithEquatableElement.SubSequence : Collection,
476+
CollectionWithEquatableElement.SubSequence.Iterator.Element
477+
== CollectionWithEquatableElement.Iterator.Element {
475478

476479
var testNamePrefix = testNamePrefix
477480

@@ -1182,7 +1185,10 @@ self.test("\(testNamePrefix).OperatorPlus") {
11821185
C.Indices.Iterator.Element == C.Index,
11831186
C.Indices.Index == C.Index,
11841187
C.Indices.SubSequence == C.Indices,
1185-
CollectionWithEquatableElement.Iterator.Element : Equatable {
1188+
CollectionWithEquatableElement.Iterator.Element : Equatable,
1189+
CollectionWithEquatableElement.SubSequence : Collection,
1190+
CollectionWithEquatableElement.SubSequence.Iterator.Element
1191+
== CollectionWithEquatableElement.Iterator.Element {
11861192

11871193
var testNamePrefix = testNamePrefix
11881194

@@ -1311,7 +1317,10 @@ self.test("\(testNamePrefix).removeLast(n: Int)/whereIndexIsBidirectional/remove
13111317
C.Indices.Iterator.Element == C.Index,
13121318
C.Indices.Index == C.Index,
13131319
C.Indices.SubSequence == C.Indices,
1314-
CollectionWithEquatableElement.Iterator.Element : Equatable {
1320+
CollectionWithEquatableElement.Iterator.Element : Equatable,
1321+
CollectionWithEquatableElement.SubSequence : Collection,
1322+
CollectionWithEquatableElement.SubSequence.Iterator.Element
1323+
== CollectionWithEquatableElement.Iterator.Element {
13151324

13161325
var testNamePrefix = testNamePrefix
13171326

stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,9 @@ extension TestSuite {
14631463
resiliencyChecks: CollectionMisuseResiliencyChecks = .all
14641464
) where
14651465
SequenceWithEquatableElement.Iterator.Element : Equatable,
1466+
SequenceWithEquatableElement.SubSequence : Sequence,
1467+
SequenceWithEquatableElement.SubSequence.Iterator.Element
1468+
== SequenceWithEquatableElement.Iterator.Element,
14661469
S.SubSequence : Sequence,
14671470
S.SubSequence.Iterator.Element == S.Iterator.Element,
14681471
S.SubSequence.SubSequence == S.SubSequence {
@@ -1615,6 +1618,25 @@ self.test("\(testNamePrefix).dropLast/semantics/negative") {
16151618
_ = s.dropLast(-1)
16161619
}
16171620

1621+
//===----------------------------------------------------------------------===//
1622+
// drop(while:)
1623+
//===----------------------------------------------------------------------===//
1624+
1625+
self.test("\(testNamePrefix).drop(while:)/semantics").forEach(in: findTests) {
1626+
test in
1627+
let s = makeWrappedSequenceWithEquatableElement(test.sequence)
1628+
let closureLifetimeTracker = LifetimeTracked(0)
1629+
let remainingSequence = s.drop {
1630+
_blackHole(closureLifetimeTracker)
1631+
return $0 != wrapValueIntoEquatable(test.element)
1632+
}
1633+
let remaining = Array(remainingSequence)
1634+
let expectedSuffix = test.sequence.suffix(
1635+
from: test.expected ?? test.sequence.endIndex)
1636+
expectEqual(expectedSuffix.count, remaining.count)
1637+
expectEqualSequence(expectedSuffix.map(wrapValueIntoEquatable), remaining)
1638+
}
1639+
16181640
//===----------------------------------------------------------------------===//
16191641
// prefix()
16201642
//===----------------------------------------------------------------------===//
@@ -1654,6 +1676,25 @@ self.test("\(testNamePrefix).prefix/semantics/negative") {
16541676
_ = s.prefix(-1)
16551677
}
16561678

1679+
//===----------------------------------------------------------------------===//
1680+
// prefix(while:)
1681+
//===----------------------------------------------------------------------===//
1682+
1683+
self.test("\(testNamePrefix).prefix(while:)/semantics").forEach(in: findTests) {
1684+
test in
1685+
let s = makeWrappedSequenceWithEquatableElement(test.sequence)
1686+
let closureLifetimeTracker = LifetimeTracked(0)
1687+
let remainingSequence = s.prefix {
1688+
_blackHole(closureLifetimeTracker)
1689+
return $0 != wrapValueIntoEquatable(test.element)
1690+
}
1691+
let expectedPrefix = test.sequence.prefix(
1692+
upTo: test.expected ?? test.sequence.endIndex)
1693+
let remaining = Array(remainingSequence)
1694+
expectEqual(expectedPrefix.count, remaining.count)
1695+
expectEqualSequence(expectedPrefix.map(wrapValueIntoEquatable), remaining)
1696+
}
1697+
16571698
//===----------------------------------------------------------------------===//
16581699
// suffix()
16591700
//===----------------------------------------------------------------------===//

stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift.gyb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,8 @@ public class SequenceLog {
8585
public static var first = TypeIndexed(0)
8686
public static var dropFirst = TypeIndexed(0)
8787
public static var dropLast = TypeIndexed(0)
88+
public static var dropWhile = TypeIndexed(0)
89+
public static var prefixWhile = TypeIndexed(0)
8890
public static var prefixMaxLength = TypeIndexed(0)
8991
public static var suffixMaxLength = TypeIndexed(0)
9092
public static var split = TypeIndexed(0)
@@ -262,11 +264,25 @@ public struct ${Self}<
262264
Log.dropLast[selfType] += 1
263265
return base.dropLast(n)
264266
}
267+
268+
public func drop(
269+
while predicate: @noescape (Base.Iterator.Element) throws -> Bool
270+
) rethrows -> SubSequence {
271+
Log.dropWhile[selfType] += 1
272+
return try base.drop(while: predicate)
273+
}
265274

266275
public func prefix(_ maxLength: Int) -> SubSequence {
267276
Log.prefixMaxLength[selfType] += 1
268277
return base.prefix(maxLength)
269278
}
279+
280+
public func prefix(
281+
while predicate: @noescape (Base.Iterator.Element) throws -> Bool
282+
) rethrows -> SubSequence {
283+
Log.prefixWhile[selfType] += 1
284+
return try base.prefix(while: predicate)
285+
}
270286

271287
public func suffix(_ maxLength: Int) -> SubSequence {
272288
Log.suffixMaxLength[selfType] += 1

stdlib/public/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ set(SWIFTLIB_ESSENTIAL
4444
CString.swift
4545
CTypes.swift
4646
DebuggerSupport.swift
47+
DropWhile.swift.gyb
4748
EmptyCollection.swift
4849
Equatable.swift
4950
ErrorType.swift
@@ -88,6 +89,7 @@ set(SWIFTLIB_ESSENTIAL
8889
OutputStream.swift
8990
Pointer.swift
9091
Policy.swift
92+
PrefixWhile.swift.gyb
9193
Print.swift
9294
RandomAccessCollection.swift
9395
Range.swift.gyb

stdlib/public/core/Collection.swift

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1319,6 +1319,25 @@ extension Collection {
13191319
offsetBy: numericCast(amount), limitedBy: endIndex) ?? endIndex
13201320
return self[startIndex..<end]
13211321
}
1322+
1323+
/// Returns a subsequence by skipping elements while `predicate` returns
1324+
/// `true` and returning the remaining elements.
1325+
///
1326+
/// - Parameter predicate: A closure that takes an element of the
1327+
/// sequence as its argument and returns `true` if the element should
1328+
/// be skipped or `false` if it should be included. Once the predicate
1329+
/// returns `false` it will not be called again.
1330+
///
1331+
/// - Complexity: O(*n*), where *n* is the length of the collection.
1332+
public func drop(
1333+
while predicate: @noescape (Iterator.Element) throws -> Bool
1334+
) rethrows -> SubSequence {
1335+
var start = startIndex
1336+
while try start != endIndex && predicate(self[start]) {
1337+
formIndex(after: &start)
1338+
}
1339+
return self[start..<endIndex]
1340+
}
13221341

13231342
/// Returns a subsequence, up to the specified maximum length, containing
13241343
/// the initial elements of the collection.
@@ -1344,6 +1363,25 @@ extension Collection {
13441363
offsetBy: numericCast(maxLength), limitedBy: endIndex) ?? endIndex
13451364
return self[startIndex..<end]
13461365
}
1366+
1367+
/// Returns a subsequence containing the initial elements until `predicate`
1368+
/// returns `false` and skipping the remaining elements.
1369+
///
1370+
/// - Parameter predicate: A closure that takes an element of the
1371+
/// sequence as its argument and returns `true` if the element should
1372+
/// be included or `false` if it should be excluded. Once the predicate
1373+
/// returns `false` it will not be called again.
1374+
///
1375+
/// - Complexity: O(*n*), where *n* is the length of the collection.
1376+
public func prefix(
1377+
while predicate: @noescape (Iterator.Element) throws -> Bool
1378+
) rethrows -> SubSequence {
1379+
var end = startIndex
1380+
while try end != endIndex && predicate(self[end]) {
1381+
formIndex(after: &end)
1382+
}
1383+
return self[startIndex..<end]
1384+
}
13471385

13481386
/// Returns a subsequence, up to the given maximum length, containing the
13491387
/// final elements of the collection.

0 commit comments

Comments
 (0)