Skip to content

Commit bebe750

Browse files
committed
Merge pull request #2529 from russbishop/se0032
[stdlib] SR-1519: Implement SE-0032: Add Sequence.first(predicate:)
2 parents 354f6ea + da3f51f commit bebe750

File tree

4 files changed

+84
-2
lines changed

4 files changed

+84
-2
lines changed

stdlib/private/StdlibCollectionUnittest/CheckSequenceType.swift

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,9 @@ public struct FindTest {
127127
) {
128128
self.expected = expected
129129
self.element = MinimalEquatableValue(element)
130-
self.sequence = sequence.map(MinimalEquatableValue.init)
130+
self.sequence = sequence.enumerated().map {
131+
return MinimalEquatableValue($1, identity: $0)
132+
}
131133
self.expectedLeftoverSequence = expectedLeftoverSequence.map(
132134
MinimalEquatableValue.init)
133135
self.loc = SourceLoc(file, line, comment: "test data")
@@ -1771,6 +1773,30 @@ self.test("\(testNamePrefix).forEach/semantics") {
17711773
}
17721774
}
17731775

1776+
//===----------------------------------------------------------------------===//
1777+
// first()
1778+
//===----------------------------------------------------------------------===//
1779+
1780+
self.test("\(testNamePrefix).first/semantics") {
1781+
for test in findTests {
1782+
let s = makeWrappedSequenceWithEquatableElement(test.sequence)
1783+
let closureLifetimeTracker = LifetimeTracked(0)
1784+
let found = s.first {
1785+
_blackHole(closureLifetimeTracker)
1786+
return $0 == wrapValueIntoEquatable(test.element)
1787+
}
1788+
expectEqual(
1789+
test.expected == nil ? nil : wrapValueIntoEquatable(test.element),
1790+
found,
1791+
stackTrace: SourceLocStack().with(test.loc))
1792+
if test.expected != nil {
1793+
expectEqual(
1794+
test.expected, (found as? MinimalEquatableValue)?.identity,
1795+
"find() should find only the first element matching its predicate")
1796+
}
1797+
}
1798+
}
1799+
17741800
//===----------------------------------------------------------------------===//
17751801
// _preprocessingPass()
17761802
//===----------------------------------------------------------------------===//

stdlib/private/StdlibCollectionUnittest/LoggingWrappers.swift.gyb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ public class SequenceLog {
8181
public static var map = TypeIndexed(0)
8282
public static var filter = TypeIndexed(0)
8383
public static var forEach = TypeIndexed(0)
84+
public static var first = TypeIndexed(0)
8485
public static var dropFirst = TypeIndexed(0)
8586
public static var dropLast = TypeIndexed(0)
8687
public static var prefixMaxLength = TypeIndexed(0)
@@ -113,7 +114,6 @@ public class CollectionLog : SequenceLog {
113114
public static var isEmpty = TypeIndexed(0)
114115
public static var count = TypeIndexed(0)
115116
public static var _customIndexOfEquatableElement = TypeIndexed(0)
116-
public static var first = TypeIndexed(0)
117117
public static var advance = TypeIndexed(0)
118118
public static var advanceLimit = TypeIndexed(0)
119119
public static var distance = TypeIndexed(0)
@@ -243,6 +243,13 @@ public struct ${Self}<
243243
Log.forEach[selfType] += 1
244244
try base.forEach(body)
245245
}
246+
247+
public func first(
248+
where predicate: @noescape (Base.Iterator.Element) throws -> Bool
249+
) rethrows -> Base.Iterator.Element? {
250+
Log.first[selfType] += 1
251+
return try base.first(where: predicate)
252+
}
246253

247254
public typealias SubSequence = Base.SubSequence
248255

stdlib/public/core/Sequence.swift

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,17 @@ public protocol Sequence {
564564
isSeparator: @noescape (Iterator.Element) throws -> Bool
565565
) rethrows -> [SubSequence]
566566

567+
/// Returns the first element of the sequence that satisfies the given
568+
/// predicate or nil if no such element is found.
569+
///
570+
/// - Parameter where: A closure that takes an element of the
571+
/// sequence as its argument and returns a Boolean value indicating
572+
/// whether the element is a match.
573+
/// - Returns: The first match or `nil` if there was no match.
574+
func first(
575+
where: @noescape (Iterator.Element) throws -> Bool
576+
) rethrows -> Iterator.Element?
577+
567578
@warn_unused_result
568579
func _customContainsEquatableElement(
569580
_ element: Iterator.Element
@@ -959,6 +970,34 @@ extension Sequence {
959970
}
960971
}
961972

973+
internal enum _StopIteration : ErrorProtocol {
974+
case stop
975+
}
976+
977+
extension Sequence {
978+
/// Returns the first element of the sequence that satisfies the given
979+
/// predicate or nil if no such element is found.
980+
///
981+
/// - Parameter where: A closure that takes an element of the
982+
/// sequence as its argument and returns a Boolean value indicating
983+
/// whether the element is a match.
984+
/// - Returns: The first match or `nil` if there was no match.
985+
public func first(
986+
where predicate: @noescape (Iterator.Element) throws -> Bool
987+
) rethrows -> Iterator.Element? {
988+
var foundElement: Iterator.Element? = nil
989+
do {
990+
try self.forEach {
991+
if try predicate($0) {
992+
foundElement = $0
993+
throw _StopIteration.stop
994+
}
995+
}
996+
} catch is _StopIteration { }
997+
return foundElement
998+
}
999+
}
1000+
9621001
extension Sequence where Iterator.Element : Equatable {
9631002
/// Returns the longest possible subsequences of the sequence, in order,
9641003
/// around elements equal to the given element. Elements that are used to

validation-test/stdlib/SequenceType.swift.gyb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -944,6 +944,16 @@ SequenceTypeTests.test("forEach/dispatch") {
944944
expectCustomizable(tester, tester.log.forEach)
945945
}
946946

947+
//===----------------------------------------------------------------------===//
948+
// first()
949+
//===----------------------------------------------------------------------===//
950+
951+
SequenceTypeTests.test("first/dispatch") {
952+
let tester = SequenceLog.dispatchTester([OpaqueValue(1)])
953+
tester.first { $0.value == 1 }
954+
expectCustomizable(tester, tester.log.first)
955+
}
956+
947957
//===----------------------------------------------------------------------===//
948958
// dropFirst()
949959
//===----------------------------------------------------------------------===//

0 commit comments

Comments
 (0)