快速数组-检查是否存在索引

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

但我明白

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

110975 次浏览

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

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

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

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

斯威夫特的一个优雅方式:

let isIndexValid = array.indices.contains(index)

添加一些延伸糖:

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风格的编码时,增强了可读性

类型扩展:

extension Collection {


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


}

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

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
}

断言是否存在数组索引:

如果您不想添加扩展 Sugar,那么这种方法是非常好的:

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

迅捷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

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
}


}

处理 外界索引是我的工作。

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

我相信现有的答案可以进一步改进,因为这个功能可能需要在 很多地方中的代码库(重复常用操作时的代码气味)。所以考虑添加我自己的实现,并推理为什么我考虑这种方法(效率优良的 API 设计的重要组成部分,如果可能的话应该首选 只要不严重影响可读性)。除了使用类型本身的方法强制 好面向对象设计之外,我认为协议扩展非常棒,我们甚至可以使现有的答案为 更加迅捷。限制扩展是伟大的,因为你 不要创建不使用的代码。使代码更简洁和可扩展通常可以使维护更容易,但是还有 权衡利弊(简洁是我首先想到的)。

因此,您可以注意到,如果您希望使用 可重用性的扩展思想,但是更喜欢上面引用的 contains方法,那么您可以使用 修改这个答案。我试图使这个答案灵活地用于不同的用途。

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 的回答确实非常优雅,但它使用了一个额外的间接层(见 给你)。索引属性的作用类似于从 startIndexendIndex创建的引擎盖下的 CountableRange<Int>,没有理由解释这个问题(空间复杂度略高,特别是如果 String很长)。也就是说,时间复杂度应该与 endIndexstartIndex属性之间的直接比较大致相同,因为 N = 2,即使 contains(_:)对于 Collection来说是 O (N)(Range对于开始和结束索引只有两个属性)。

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

extension Collection {
func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}

注意这里我是如何使用 startIndex而不是0的-这是为了支持 ArraySlice和其他 SubSequence类型。这是 另一个动机发布一个解决方案。

示例用法:

let check = digits.isIndexValid(index: index)

对于一般的 Collection来说,在 Swift 中很难创建无效的 Index,因为苹果限制了 Collectionassociatedtype Index的初始化器——这些初始化器只能从现有的有效 Collection.Index(如 startIndex)创建。

也就是说,对 Array使用原始 Int索引是很常见的,因为需要检查随机 Array索引的实例很多。因此,您可能希望将该方法限制为更少的结构..。

极限方法范围

你会注意到这个解决方案适用于所有的 Collection类型(可扩展性) ,但是只有当你想限制你的特定应用程序的作用域时(例如,如果你不想添加 String方法,因为你不需要它) ,你才能把它限制在 Array中。

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,但是您也可以反过来显式地使用 String。同样,最好将函数限制在您专门用于 避免代码蠕变的内容上。

跨不同模块引用该方法

编译器已经应用了多种优化来防止泛型成为通常的问题,但是当从单独的模块调用代码时,这些优化并不适用。对于这样的情况,使用 @inlinable可以以增加框架二进制大小为代价提高性能。一般来说,如果您希望 真的提高性能,并且希望将该函数封装在 优秀的 SOC的单独 Xcode 目标中,那么您可以尝试:

extension Collection where Element: Numeric {
// Add this signature to the public header of the extensions module as well.
@inlinable public func isIndexValid(index: Index) -> Bool {
return self.endIndex > index && self.startIndex <= index
}
}

我可以建议尝试一个 模块化代码基结构,我认为它有助于确保单一责任(和 很可靠)在项目中的常见操作。我们可以尝试遵循 给你步骤,这就是我们可以使用这种优化的地方(尽管有所保留)。可以为这个函数使用属性,因为每个调用站点的编译器操作 只增加了一行代码,但是它可以进一步提高性能,因为方法没有添加到调用堆栈中(所以不需要跟踪)。如果您需要前沿速度,并且您不介意小的二进制大小增加,那么这是非常有用的。(- : 或者可以尝试新的 XCFrameworks (但要注意 < iOS 13的 ObjecC 运行时兼容性)。

最好的方式。

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

我认为我们应该把这个扩展添加到 Swift 的每个项目中

extension Collection {
@inlinable func isValid(position: Self.Index) -> Bool {
return (startIndex..<endIndex) ~= position
}
    

@inlinable func isValid(bounds: Range<Self.Index>) -> Bool {
return (startIndex..<endIndex) ~= bounds.upperBound
}
    

@inlinable subscript(safe position: Self.Index) -> Self.Element? {
guard isValid(position: position) else { return nil }
return self[position]
}
    

@inlinable subscript(safe bounds: Range<Self.Index>) -> Self.SubSequence? {
guard isValid(bounds: bounds) else { return nil }
return self[bounds]
}
}


extension MutableCollection {
@inlinable subscript(safe position: Self.Index) -> Self.Element? {
get {
guard isValid(position: position) else { return nil }
return self[position]
}
set {
guard isValid(position: position), let newValue = newValue else { return }
self[position] = newValue
}
}
@inlinable subscript(safe bounds: Range<Self.Index>) -> Self.SubSequence? {
get {
guard isValid(bounds: bounds) else { return nil }
return self[bounds]
}
set {
guard isValid(bounds: bounds), let newValue = newValue else { return }
self[bounds] = newValue
}
}
}

请注意,我的 isValid(position:)isValid(bounds:)函数是一个复杂的 O(1),不像下面的大多数答案,它使用的 contains(_:)方法是一个复杂的 O(n)


示例用法:

let arr = ["a","b"]
print(arr[safe: 2] ?? "nil") // output: nil
print(arr[safe: 1..<2] ?? "nil") // output: nil


var arr2 = ["a", "b"]
arr2[safe: 2] = "c"
print(arr2[safe: 2] ?? "nil") // output: nil
arr2[safe: 1..<2] = ["c","d"]
print(arr[safe: 1..<2] ?? "nil") // output: nil