在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
}
但是我得到了
致命错误:数组索引超出范围
答案 0 :(得分:348)
Swift的优雅方式:
let isIndexValid = array.indices.contains(index)
答案 1 :(得分:45)
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)
对我来说,我更喜欢方法。
// 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
方法,您可以<强>重做这个答案。我试图让这个答案灵活用于不同的用途。
您可以使用更高效的算法(空间和时间)并使用具有通用约束的协议扩展使其可扩展:
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
很长) .话虽如此,时间复杂度应该与 endIndex
和 startIndex
属性之间的直接比较大致相同,因为 N = 2 即使 contains(_:)
对于 {{1} 是 O(N) }}s(Collection
s 只有开始和结束索引的两个属性)。
为了获得最佳的空间和时间复杂度、更多的可扩展性和稍长的代码,我建议使用以下内容:
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 需要被跟踪)。如果您需要最前沿的速度,并且您不介意小二进制大小的增加,这将非常有用。 (-: 或者尝试新的 @inlinable
s(但要注意
答案 8 :(得分:1)
对于我来说,我认为这是最安全的解决方案:
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)