在 Swift 数组中查找第一个符合条件的元素(例如 EKSource)

52

我想要用一行Swift表达式找到类型为EKSourceType.Local的第一个EKSource。以下是我目前拥有的代码:

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

有没有更好的方法可以做到这一点(例如不需要映射和/或使用find的通用版本)?

7个回答

123

另外在Swift4中您可以使用:

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

16
在Swift4中,这变成了:let local = eventStore.sources.first {$0.sourceType == .Local}。 (注:$0表示闭包中的第一个参数,这里指sources数组中的元素) - Nick Gaens
这是正确的答案,使用 filter 然后 first 是次优的,因为它强制执行最坏情况下的 n 时间复杂度。这意味着即使在找到元素之后,filter 仍将循环遍历数组的其余部分。first(where:{}) 短路遍历并在找到第一个匹配项后中断。 - Joe

40

有一个带有谓词闭包的 indexOf 版本 - 使用它来查找第一个本地源的索引(如果存在),然后使用该索引在 eventStore.sources 上执行操作:

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

或者,您可以通过对SequenceType进行扩展来添加通用的find方法:

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

(为什么这个还没有出现?)


我不确定为什么,但可能是因为“SequenceType对符合类型是否会被迭代地‘消耗’没有要求。为确保非破坏性迭代,请将您的序列约束为CollectionType。” - user3441734
还有缺少 try 部分(谓词可能会抛出异常) - user3441734
@user3441734:try 的问题已经解决了,你发现得不错。所有的序列操作都会消耗一个序列(map、contains 等等),所以我认为这不是问题所在。indexOf 很好用,但仅限于集合。 - Nate Cook
13
好消息,Swift 3 现在在序列中包含了一个 first(where:) 方法,可以做到这一点。 - Nate Cook
2
提案链接:https://github.com/apple/swift-evolution/blob/master/proposals/0032-sequencetype-find.md - Daniel Galasko
显示剩余2条评论

22

我不明白为什么你要使用map,为什么不使用filter呢?这样你最终将得到所有本地源,但实际上可能只有一个或者没有,你可以通过请求第一个源(如果没有,则是nil)来轻松找出:

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

29
尽管这是最直接的解决方案,但它的缺点是始终过滤整个集合,即使只需要第一个匹配的元素。我并不主张过早地进行优化,但对于大型集合来说,这可能很重要。 - Daniel Rinser
4
我同意,我更喜欢Nate Cook的回答。 - matt
7
在这里,“过早的优化”并不适用——完全避免不必要的工作既不算是过早,也不算是优化。 - Christopher Swasey
1
你可以这样写:let local = eventStore.sources.lazy.filter{$0.sourceType == .Local}.first - Jeroen Leenarts

21

Swift 4解决方案,也可以处理数组中没有元素与您的条件匹配的情况:

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

6

Swift 5:如果要从模型数组中查找,请指定 $0.keyTofound,否则请使用 $0

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

3

让我们尝试一些更加实用的内容:

let arr = [0,1,2,3]
let result = arr.lazy.map { print(""); return $0 }.first(where: { $0 == 2 })
print(result) // 3x  then 2
这有什么酷的?
当你进行搜索时,你可以访问元素或 i 标签。并且它是函数式的。

1

对于Swift 3,您需要对Nate上面的答案进行一些小的更改。以下是Swift 3版本:

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
    }
}

变更: SequenceType 改为 SequenceSelf.Generator.Element 改为 Iterator.Element


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接