Elegant way of finding the first value of a given type in a generic array

Hey there.

Simple question. Knowing the following, how would you find the first element of a given type, say Int, without loosing the type info:

let values: [AnyHashable] = ["foo", 42, "bar"]

I see a couple approaches but I don't feel satisfied by either:

values.compactMap { $0 as? Int }.first          // 1.
values.first(where: { $0 is Int }) as? Int      // 2.

#1 feels like a waste because there are potentially many useless cast.
#2 is better but the type info is lost, so it requires to add the as? Int.

I would have like to write values.first(as: Int.self).

Do you see a better way to do that?

Perhaps you could use lazy

values.lazy.compactMap { $0 as? Int }.first
4 Likes

You're right using lazy is nice. And with a short extension on Sequence:

extension Sequence {

    func first<T>(as: T.Type) -> T? {
        lazy.compactMap { $0 as? T }.first
    }

    func last<T>(as: T.Type) -> T? {
        lazy.compactMap { $0 as? T }.last
    }
}

I can write values.first(as: Int.self) which I think has the advantage of being concise while remaining clear.

Thanks @Lantua for the suggestion.

I do not understand the interest of lazy if it is to take the last matching item in the collection.

It's markedly less than first, but you can still avoid creating intermediate array with it.

I've used nearly this exact extension for a while. It's really helpful with UIKit. e.g.

public extension UINib {
  static func instantiate<Object: AnyObject>(owner: Any? = nil) -> Object? {
    Bundle(for: Object.self)
      .loadNibNamed("\(Object.self)", owner: owner)?
      .getFirst()
  }
}

However, it's not actually first. It's getFirst. first is the result of getFirst. And you shouldn't require an argument when you don't need it.

let array: [Any] = [1, "🥇"]
XCTAssertEqual(array.getFirst(), "🥇")

let getFirstInt = { array.getFirst(Int.self) }
XCTAssertEqual(getFirstInt(), 1)
public extension Sequence {
  /// The first element of a given type.
  func getFirst<T>(_: T.Type = T.self) -> T? {
    lazy.compactMap { $0 as? T } .first
  }
}