Find first element matching condition in Swift array (e.g. EKSource)

49,830

Solution 1

There's a version of indexOf that takes a predicate closure - use it to find the index of the first local source (if it exists), and then use that index on eventStore.sources:

if let index = eventStore.sources.indexOf({ $0.sourceType == .Local }) {
    let eventSourceForLocal = eventStore.sources[index]
}

Alternately, you could add a generic find method via an extension on SequenceType:

extension SequenceType {
    func find(@noescape predicate: (Self.Generator.Element) throws -> Bool) rethrows -> Self.Generator.Element? {
        for element in self {
            if try predicate(element) {
                return element
            }
        }
        return nil
    }
}

let eventSourceForLocal = eventStore.sources.find({ $0.sourceType == .Local })

(Why isn't this there already?)

Solution 2

Alternatively in Swift3 you could use:

let local = eventStore.sources.first(where: {$0.sourceType == .Local}) 

Solution 3

I don't understand why you're using map at all. Why not use filter? You will then end up with all the local sources, but in actual fact there will probably be only one, or none, and you can readily find out by asking for the first one (it will be nil if there isn't one):

let local = eventStore.sources.filter{$0.sourceType == .Local}.first

Solution 4

Swift 4 solution that also handles the situation when there are no elements in your array that match your condition:

if let firstMatch = yourArray.first{$0.id == lookupId} {
  print("found it: \(firstMatch)")
} else {
  print("nothing found :(")
}

Solution 5

Swift 5 If you want to find out from Array of Model then speciyfy $0.keyTofound otherwise use $0

if let index = listArray.firstIndex(where: { $0.id == lookupId }) {
     print("Found at \(index)")
} else {
     print("Not found")
 }
Share:
49,830

Related videos on Youtube

Drux
Author by

Drux

Updated on July 09, 2022

Comments

  • Drux
    Drux almost 2 years

    I would like to find the first EKSource of type EKSourceType.Local with a "single"-line expression in Swift. Here is what I currently have:

    let eventSourceForLocal = 
        eventStore.sources[eventStore.sources.map({ $0.sourceType })
            .indexOf(EKSourceType.Local)!]
    

    Is there a better way of doing this (such as without mapping and/or with a generic version of find)?

  • user3441734
    user3441734 over 8 years
    i am not sure why, but maybe because "SequenceType makes no requirement on conforming types regarding whether they will be destructively "consumed" by iteration. To ensure non-destructive iteration, constrain your sequence to CollectionType."
  • user3441734
    user3441734 over 8 years
    also there is missing try part (predicate can throw)
  • Nate Cook
    Nate Cook over 8 years
    @user3441734: Good catch on the try, fixed. All the sequence operations will consume a sequence (map, contains, etc), so I don't think that's it. indexOf works fine but is limited to collections.
  • Daniel Rinser
    Daniel Rinser about 8 years
    While this is the most straight-forward solution, it has the disadvantage of always filtering the whole collection, even though it only needs the first matching element. I'm not advocating for premature optimization, but for large collections that might matter.
  • matt
    matt about 8 years
    @DanielRinser I agree, I like Nate Cook's answer better.
  • Christopher Swasey
    Christopher Swasey about 8 years
    IMO premature optimization doesn't even come into play here—avoiding completely unnecessary work is neither premature nor optimization.
  • Daniel Galasko
    Daniel Galasko almost 8 years
    I called mine findFirst because thats a bit more self documenting:)
  • Nate Cook
    Nate Cook almost 8 years
    @DanielGalasko Good news, Swift 3 now includes a first(where:) method on sequences that does just this.
  • Daniel Galasko
    Daniel Galasko almost 8 years
    thats great @NateCook !
  • Daniel Galasko
    Daniel Galasko almost 8 years
  • Jeroen Leenarts
    Jeroen Leenarts over 7 years
    So you make it let local = eventStore.sources.lazy.filter{$0.sourceType == .Local}.first
  • Nick Gaens
    Nick Gaens over 6 years
    In Swift4, that becomes: let local = eventStore.sources.first {$0.sourceType == .Local}
  • Joe
    Joe about 5 years
    This is the correct answer, using filter then first is sub optimal because it forces worst case of n time complexity. That means that even after you find the element, filter will still loop through the rest of the array. first(where:{}) short circuits traversal and breaks after it finds the first occurrence.