51

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)?

Drux
  • 11,992
  • 13
  • 66
  • 116

7 Answers7

119

Alternatively in Swift4 you could use:

let local = eventStore.sources.first {$0.sourceType == .Local}
Jarno
  • 6,243
  • 3
  • 42
  • 57
Bueno
  • 1,840
  • 2
  • 15
  • 17
  • 16
    In Swift4, that becomes: `let local = eventStore.sources.first {$0.sourceType == .Local}` – Nick Gaens Feb 09 '18 at 13:51
  • 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. – Joe May 08 '19 at 15:22
40

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?)

Nate Cook
  • 92,417
  • 32
  • 217
  • 178
22

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
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 29
    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. – Daniel Rinser Apr 06 '16 at 09:06
  • 4
    @DanielRinser I agree, I like Nate Cook's answer better. – matt Apr 06 '16 at 16:55
  • 7
    IMO premature optimization doesn't even come into play here—avoiding completely unnecessary work is neither premature nor optimization. – Christopher Swasey May 09 '16 at 14:14
  • 1
    So you make it `let local = eventStore.sources.lazy.filter{$0.sourceType == .Local}.first` – Jeroen Leenarts Nov 21 '16 at 19:34
21

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 :(")
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
6

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")
 }
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
3

Let's try something more functional:

let arr = [0,1,2,3]
let result = arr.lazy.map { print(""); return $0 }.first(where: { $0 == 2 })
print(result) // 3x  then 2

Whats cool about this?
You get access to element or i while you search. And it's functional.

Sentry.co
  • 5,355
  • 43
  • 38
1

For Swift 3 you'll need to make a few small changes to Nate's answer above. Here's the Swift 3 version:

public extension Sequence {
    func find(predicate: (Iterator.Element) throws -> Bool) rethrows -> Iterator.Element? {
        for element in self {
            if try predicate(element) {
                return element
            }
        }
        return nil
    }
}

Changes: SequenceType > Sequence, Self.Generator.Element > Iterator.Element

Tyler Sheaffer
  • 1,953
  • 1
  • 16
  • 16