Swift Array - 检查索引是否存在

时间:2014-09-22 14:46:12

标签: swift

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

但是我得到了

  

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

11 个答案:

答案 0 :(得分:348)

Swift的优雅方式:

let isIndexValid = array.indices.contains(index)

答案 1 :(得分:45)

Swift 3& 4分机:

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
}

答案 2 :(得分:28)

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

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

答案 3 :(得分:10)

添加一些延长糖:

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: "c"
//or with guard:
guard let anotherItem = ["a","b","c","d"][safe:3] else {return}
print(anotherItem)//"c"

与数组一起进行if let样式编码时,增强了可读性

答案 4 :(得分:6)

你可以用更安全的方式重写这个来检查数组的大小,并使用三元条件:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?

答案 5 :(得分:4)

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 :(得分: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

答案 7 :(得分:2)

我相信现有的答案可以进一步改进,因为在代码库中的多个地方可能需要此功能(重复常见操作时的代码异味)。所以想到添加我自己的实现,并解释我为什么考虑这种方法(效率良好的 API 设计的重要组成部分,应该在可能的情况下首选只要可读性不会受到太大影响)。除了通过类型本身的方法强制良好的面向对象设计之外,我认为协议扩展很棒,我们可以使现有的答案更加快速。限制扩展程序很棒,因为您不会创建您不使用的代码。使代码更简洁和可扩展通常可以使维护更容易,但存在权衡(我首先想到的是简洁)。

因此,您可以注意到,如果您喜欢使用扩展理念来实现可重用性,但更喜欢上面引用的 contains 方法,您可以<强>重做这个答案。我试图让这个答案灵活用于不同的用途。

TL;DR

您可以使用更高效的算法(空间和时间)并使用具有通用约束的协议扩展使其可扩展:

extension Collection where Element: Numeric { // Constrain only to numerical collections i.e Int, CGFloat, Double and NSNumber
  func isIndexValid(index: Index) -> Bool {
    return self.endIndex > index && self.startIndex <= index
  }
}

// Usage

let checkOne = digits.isIndexValid(index: index)
let checkTwo = [1,2,3].isIndexValid(index: 2)

深入探讨

效率

@Manuel 的回答确实非常优雅,但它使用了额外的间接层(参见 here)。索引属性就像 CountableRange<Int>startIndex 创建的引擎盖下的 endIndex 没有这个问题的原因(空间复杂度略高,特别是如果 String 很长) .话虽如此,时间复杂度应该与 endIndexstartIndex 属性之间的直接比较大致相同,因为 N = 2 即使 contains(_:) 对于 {{1} 是 O(N) }}s(Collections 只有开始和结束索引的两个属性)。

为了获得最佳的空间和时间复杂度、更多的可扩展性和稍长的代码,我建议使用以下内容:

Range

请注意我如何使用 extension Collection { func isIndexValid(index: Index) -> Bool { return self.endIndex > index && self.startIndex <= index } } 而不是 0 - 这是为了支持 startIndex 和其他 ArraySlice 类型。这是发布解决方案的另一个动机

示例用法:

SubSequence

对于一般的 let check = digits.isIndexValid(index: index) 而言,在 Swift 中通过设计创建无效的 Collection 非常困难,因为 Apple 已将 Index 的初始值设定项限制在 associatedtype Index 上 - 可以只能从现有的有效 Collection(如 Collection.Index)创建。

话虽如此,对于 startIndex 使用原始 Int 索引是很常见的,因为在很多情况下您需要检查随机 Array 索引。因此,您可能希望将该方法限制为更少的结构...

限制方法范围

您会注意到此解决方案适用于所有 Array 类型(可扩展性),但仅当您想限制特定应用的范围(例如,如果您不想要添加的 Collection 方法,因为您不需要它)。

Array

对于 String,您不需要显式使用 extension Array { func isIndexValid(index: Index) -> Bool { return self.endIndex > index && self.startIndex <= index } } 类型:

Array

您可以根据自己的用例随意调整此处的代码,还有许多其他类型的 Index,例如let check = [1,2,3].isIndexValid(index: 2) 秒。您还可以使用通用约束,例如:

Collection

这将范围限制为 LazyCollection extension Collection where Element: Numeric { func isIndexValid(index: Index) -> Bool { return self.endIndex > index && self.startIndex <= index } } ,但您也可以相反地显式使用 Numeric。同样,最好将该功能限制为您专门用于避免代码蠕变的功能。

跨不同模块引用方法

编译器已经应用了多种优化来防止泛型成为一般问题,但是当从单独的模块调用代码时这些不适用。对于这种情况,使用 Collection 可以以增加框架二进制大小为代价为您带来有趣的性能提升。一般而言,如果您真的想要提高性能并希望将该功能封装在单独的 Xcode 目标中以良好的 SOC,您可以尝试:

String

我可以推荐尝试模块化代码库结构,我认为这有助于确保项目中的单一职责(和可靠)以进行常见操作。我们可以尝试按照步骤 here 操作,这就是我们可以使用此优化的地方(尽管很少)。可以为此函数使用该属性,因为编译器操作只为每个调用站点添加一行额外的代码,但它可以进一步提高性能,因为方法未添加到调用堆栈中(因此不会添加到调用堆栈中) t 需要被跟踪)。如果您需要最前沿的速度,并且您不介意小二进制大小的增加,这将非常有用。 (-: 或者尝试新的 @inlinables(但要注意

答案 8 :(得分:1)

快速4和5扩展名:

对于我来说,我认为这是最安全的解决方案:

value={{port1: this.state.Port1, port2: this.state.Port2}}

示例:

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

答案 9 :(得分:0)

确定是否存在数组索引:

如果您不想添加扩展糖,此方法非常有用:

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

答案 10 :(得分:0)

最好的方法。

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