Reputation:
If xs
is a collection and pred
is a closure that returns a Bool
, is there a built-in function that does the following?
xs.filter(pred).first
This gets the first element of a collection matching the predict, or nil
if there is no match. Not interested in the index, but the element itself.
Upvotes: 2
Views: 2781
Reputation: 92549
In the simplest case, what you want may look like this:
let array = [18, 12, 35, 11, 12, 44]
var first: Int?
for element in array where element == 12 {
first = element
break
}
print(first) // prints: Optional(12)
If you really need to set a predicate closure, you can use the following pattern:
let array = [18, 12, 35, 11, 12, 44]
var first: Int?
let predicateClosure = { (value: Int) -> Bool in
return value == 12
}
for element in array where predicateClosure(element) {
first = element
break
}
print(first) // prints: Optional(12)
If you need to repeat these operations, you can refactor your code by using a SequenceType
protocol extension:
extension SequenceType where Generator.Element == Int {
func getFirstWithPredicate(predicate: Int -> Bool) -> Int? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let array = [18, 12, 35, 11, 12, 44]
let predicateClosure: Int -> Bool = {
return $0 == 12
}
let first = array.getFirstWithPredicate(predicateClosure)
print(first) // prints: Optional(12)
Note that you don't need your predicate
closure parameter to escape your getFirstWithPredicate(_:)
method here, so you can add the @noescape
attribute before it (see Nonescaping Closures for more details):
extension SequenceType where Generator.Element == Int {
func getFirstWithPredicate(@noescape predicate: Int -> Bool) -> Int? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let array = [18, 12, 35, 11, 12, 44]
let predicateClosure = { $0 == 12 }
let first = array.getFirstWithPredicate(predicateClosure)
print(first) // prints: Optional(12)
If you want the previous code to work for any type of sequence, you can remove the Int
constraint and redeclare your getFirstWithPredicate(_:)
method like in the following example:
extension SequenceType {
func getFirstWithPredicate(@noescape predicate: Generator.Element -> Bool) -> Generator.Element? {
for element in self where predicate(element) {
return element
}
return nil
}
}
let intArray = [18, 12, 35, 11, 12, 44]
let firstInt = intArray.getFirstWithPredicate { $0 == 12 }
print(firstInt) // prints: Optional(12)
let stringArray = ["Car", "Boat", "Plane", "Boat", "Bike"]
let firstString = stringArray.getFirstWithPredicate { $0 == "Boat" }
print(firstString) // prints: Optional("Boat")
If you're using a CollectionType
protocol conforming object (for example, an Array
), you can easily get the last element that matches your predicate with the same getFirstWithPredicate(_:)
declaration by using reverse()
:
extension SequenceType {
func getFirstWithPredicate(@noescape predicate: Generator.Element -> Bool) -> Generator.Element? {
for element in self where predicate(element) {
return element
}
return nil
}
}
struct Toy {
let name: String
let price: Int
}
let array = [
Toy(name: "Ball", price: 20),
Toy(name: "Car", price: 12),
Toy(name: "Plane", price: 35),
Toy(name: "Boat", price: 12),
]
let lastToyWithMatchingPrice = array.reverse().getFirstWithPredicate { $0.price == 12 }
print(lastToyWithMatchingPrice) // prints: Optional(Toy(name: "Boat", price: 12))
Upvotes: 1
Reputation: 14983
No there isn't, but you can write one yourself like this:
extension SequenceType {
func first(@noescape pred: Generator.Element throws -> Bool) rethrows -> Generator.Element? {
return try filter(pred).first
}
}
EDIT: This version isn't optimal, since the filter
creates a whole new array, even though only the first element would be needed. As noted by Martin R, lazy.filter
also doesn't work for. This would be necessary to make it work with lazy
:
extension CollectionType {
func first(pred: Generator.Element -> Bool) -> Generator.Element? {
return lazy.filter(pred).first
}
}
Because:
@noescape
can't be used because @noescape
means that the closure cannot escape the current function, which would be possible when passing it to the lazy filter
(doesn't evaluate elements until it is asked to -> has to escape the predicate)throws
can't be used because the filter is lazy -> errors wouldn't be thrown immediately, but rather when it is used the first time which would be when calling first
, but first
can't be throwing because it's a property. There are some discussions on making getters (and subscripts) throwing in future versions of Swift.CollectionType
has to be used, because only LazyCollectionType
has the first
property.So to really make it lazy and have all the @noescape
, throws
and SequenceType
, you'd have to use an imperative approach:
extension SequenceType {
func first(@noescape pred: Generator.Element throws -> Bool) rethrows -> Generator.Element? {
for elem in self where try pred(elem) {
return elem
}
return nil
}
}
Upvotes: 2