Swift 数组 - 检查索引是否存在

182
在Swift中,有没有一种方法可以检查数组中是否存在索引而不会抛出致命错误?
我希望我可以像这样做:
let arr: [String] = ["foo", "bar"]
let str: String? = arr[1]
if let str2 = arr[2] as String? {
    // this wouldn't run
    println(str2)
} else {
    // this would be run
}

但是我遇到了

致命错误:数组索引超出范围

12个回答

552

一种优雅的Swift方式:

let isIndexValid = array.indices.contains(index)

17
在现实生活中,这并不重要,但从时间复杂度的角度来看,使用 index < array.count 不是更好吗? - funct7
22
如果您想知道速度差异是多少,我使用Xcode的工具进行了测量,结果可以忽略不计。https://gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad - Mason
9
关于这个问题,我想再补充一点:如果你在处理 ArraySlice 时采用同样的逻辑,那么第一个索引将不是0,因此执行 index >= 0 检查就不够好了。使用 .indices 可以在任何情况下都有效。 - DeFrenZ
3
我喜欢Swift的原因是它可以帮我解决这个问题。这是我的使用案例:为服务构建糟糕的JSON结构,所以必须这样做:“attendee3”:names.indices.contains(2)?names [2]:“”。 - Lee Probert
7
这是一个很好的答案。为了回答@funct7关于时间复杂度的担忧,Array的indices方法返回的类型是一个范围(range),而不是整数数组。因此,时间复杂度是O(1),就像手动检查上下界时一样。 - Chris Laurel
显示剩余6条评论

84

类型扩展:

extension Collection {

    subscript(optional i: Index) -> Iterator.Element? {
        return self.indices.contains(i) ? self[i] : nil
    }

}

使用这种方法,当你在索引中添加关键字“optional”时,可以得到一个可选值,这意味着即使索引超出范围,您的程序也不会崩溃。以你的示例为例:

let arr = ["foo", "bar"]
let str1 = arr[optional: 1] // --> str1 is now Optional("bar")
if let str2 = arr[optional: 2] {
    print(str2) // --> this still wouldn't run
} else {
    print("No string found at that index") // --> this would be printed
}

38

只需检查索引是否小于数组大小:

if 2 < arr.count {
    ...
} else {
    ...
}

4
如果数组大小未知,该怎么办? - Nathan McKaskle
10
数组始终知道它包含多少元素,因此大小不能未知。 - Antonio
20
抱歉,我当时没想清楚。早上的咖啡还在进入血液中。 - Nathan McKaskle
5
没问题,@NathanMcKaskle... 有时候我也会遇到这种情况,即使喝了几杯咖啡也没用 ;-) - Antonio
这个答案是不正确的。问题是“检查索引是否存在”。元素的数量与特定索引是否存在无关。 - Manuel
显示剩余8条评论

21

添加一些扩展糖:

extension Collection {
  subscript(safe index: Index) -> Iterator.Element? {
    guard indices.contains(index) else { return nil }
    return self[index]
  }
}
if let item = ["a", "b", "c", "d"][safe: 3] { print(item) } // Output: "d"
// or with guard:
guard let anotherItem = ["a", "b", "c", "d"][safe: 3] else { return }
print(anotherItem) // "d"

在与数组结合使用时,优化了使用if let代码风格的可读性。


3
老实说,这是最快且可读性和清晰度最高的方法。 - barndog
2
@barndog 我也很喜欢它。我通常会在任何我开始的项目中将其添加为Sugar。还添加了guard示例。感谢Swift Slack社区提出这个想法。 - Sentry.co
1
好的解决方案...但是在你给出的例子中,输出将会打印出 "d"。 - jayant rawat
1
这应该是 Swift 语言的一部分。索引超出范围的问题和 nil 值一样令人头疼。我甚至建议使用类似的语法:可能是这样的:myArray[?3]。如果 myArray 是可选的,你可以得到 myArray?[?3]。 - Andy Weinstein

11

最佳方法。

  let reqIndex = array.indices.contains(index)
  print(reqIndex)

2
请在您的答案中添加一些解释,以便其他人可以从中学习。 - Nico Haase

8

Swift 4扩展:

对于我来说,我更喜欢像方法一样的方式。

// MARK: - Extension Collection

extension Collection {

    /// Get at index object
    ///
    /// - Parameter index: Index of object
    /// - Returns: Element at index or nil
    func get(at index: Index) -> Iterator.Element? {
        return self.indices.contains(index) ? self[index] : nil
    }
}

感谢 @Benno Kress 的贡献。

6
你可以以更安全的方式重写这段代码来检查数组的大小,并且使用三元条件语句:
if let str2 = (arr.count > 2 ? arr[2] : nil) as String?

2
Antonio所建议的更加透明(和高效)。如果您有一个可用的值可以使用并消除if语句,只留下let语句,那么三元运算符就是合适的。 - David Berry
2
@David,Antonio的建议需要两个if语句,而不是原始代码中的一个。我的代码使用条件运算符替换了第二个if,让您保留单个else,而不是强制使用两个单独的else块。 - Sergey Kalinichenko
2
在他的代码中,我没有看到两个if语句,但是对于省略号,让str2 = arr [1],就完成了。如果您用Antonio的if语句替换OP if let语句,并将分配移动到内部(或不移动,因为str2的唯一使用是将其打印,根本没有必要,只需将取消引用放在println中)。更不用说三元运算符只是一个晦涩的(对某些人来说:)if / else。如果arr.count> 2,那么arr [2]永远不可能为空,所以为什么要映射为String?只是为了应用另一个if语句。 - David Berry
1
@David OP问题中的整个if语句将会出现在Antonio答案的"then"分支中,因此会有两个嵌套的if。我将OP的代码视为一个小例子,所以我假设他仍然需要一个if。我同意你的看法,在他的例子中,if是不必要的。但是再说一遍,整个语句都是无意义的,因为OP知道数组长度不够,并且它的元素都不是nil,所以他可以删除if,只保留else块。 - Sergey Kalinichenko
1
不会。Antonio的if语句替换了OP的if语句。由于数组类型是[String],因此您知道它永远不可能包含nil,因此无需检查长度以外的任何内容。 - David Berry
显示剩余6条评论

2

Swift 4和5扩展:

对我而言,我认为这是最安全的解决方案:

public extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[index] = newValue
            }
        }
    }
}

例子:

let array = ["foo", "bar"]
if let str = array[safe: 1] {
    print(str) // "bar"
} else {
    print("index out of range")
}

2
extension Array {
    func isValidIndex(_ index : Int) -> Bool {
        return index < self.count
    }
}

let array = ["a","b","c","d"]

func testArrayIndex(_ index : Int) {

    guard array.isValidIndex(index) else {
        print("Handle array index Out of bounds here")
        return
    }

}

对我来说处理indexOutOfBounds问题很容易。


2
如果索引是负数怎么办?你也应该检查一下。 - Frankie Simon

1

检查数组索引是否存在:

如果您不想添加扩展语法,这种方法非常好:

let arr = [1,2,3]
if let fourthItem = (3 < arr.count ?  arr[3] : nil ) {
     Swift.print("fourthItem:  \(fourthItem)")
}else if let thirdItem = (2 < arr.count ?  arr[2] : nil) {
     Swift.print("thirdItem:  \(thirdItem)")
}
//Output: thirdItem: 3

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