安全(边界检查)数组查找在Swift,通过可选绑定?

如果我在Swift中有一个数组,并尝试访问一个越界的索引,有一个不足为奇的运行时错误:

var str = ["Apple", "Banana", "Coconut"]


str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

然而,我本以为有了Swift带来的所有可选的链接和安全,做这样的事情是微不足道的:

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
print(nonexistent)
...do other things with nonexistent...
}

而不是:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
let nonexistent = str[theIndex] // Lookup
print(nonexistent)
...do other things with nonexistent...
}

但事实并非如此——我必须使用ol' if语句来检查并确保索引小于str.count

我尝试添加自己的subscript()实现,但我不确定如何将调用传递给原始实现,或者在不使用下标符号的情况下访问项(基于索引):

extension Array {
subscript(var index: Int) -> AnyObject? {
if index >= self.count {
NSLog("Womp!")
return nil
}
return ... // What?
}
}
128482 次浏览

我认为这不是一个好主意。似乎更可取的做法是构建稳定的代码,不导致试图应用越界索引。

请考虑让这样的错误通过返回nil无声地失败(正如上面的代码所建议的那样),很容易产生更复杂、更难以处理的错误。

你可以用类似的方式重写,用你自己的方式写下标。唯一的缺点是现有的代码将不兼容。我认为找到一个钩子来覆盖通用的x[I](也没有C中的文本预处理器)将是具有挑战性的。

我能想到的最接近的就是

// compile error:
if theIndex < str.count && let existing = str[theIndex]

编辑:这实际上是有效的。一行程序! !

func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
return idx < array.count ? array[idx] : nil
}


if let x: AnyObject = ifInBounds(swiftarray, 3) {
println(x)
}
else {
println("Out of bounds")
}

如果您确实需要这种行为,那么您需要的是Dictionary而不是Array。字典在访问缺失的键时返回nil,这是有意义的,因为要知道一个键是否存在于字典中要困难得多,因为这些键可以是任何东西,其中数组中的键必须0count的范围内。在这个范围内迭代是非常常见的,在这个范围内,确定在循环的每次迭代中都有一个真实的值。

我认为它不能这样工作的原因是Swift开发人员的设计选择。举个例子:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

如果您已经知道索引的存在,就像您在使用数组的大多数情况下所做的那样,那么这段代码非常棒。然而,如果访问下标可能返回nil,则Arraysubscript方法的是否改变了返回类型是可选的。这将更改您的代码为:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

这意味着每次遍历数组或使用已知索引执行其他操作时,都需要展开一个可选对象,因为很少会访问出界索引。Swift的设计者选择了更少的可选项的展开,以访问越界索引时的运行时异常为代价。并且崩溃比你在数据某处没有预料到的nil引起的逻辑错误更可取。

我同意他们的观点。因此,您不会更改默认的Array实现,因为您将破坏所有期望从数组中获得非可选值的代码。

相反,你可以继承Array的子类,并重写subscript以返回一个可选的。或者,更实际的是,你可以用一个非下标方法来扩展Array

extension Array {


// Safely lookup an index that might be out of bounds,
// returning nil if it does not exist
func get(index: Int) -> T? {
if 0 <= index && index < count {
return self[index]
} else {
return nil
}
}
}


var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
print("I ate a \( fruit )")
// I ate a Banana
}


if let fruit = fruits.get(3) {
print("I ate a \( fruit )")
// never runs, get returned nil
}

Swift 3更新

func get(index: Int) ->T?需要替换为func get(index: Int) ->Element?

在我的用例中,我用__abc0填充了数组:

let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]


switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
// process last component with 5
default:
break
}

还要检查Erica Sadun / Mike Ash的带有safe:标签的下标扩展:http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/

亚历克斯的回答对这个问题有很好的建议和解决方案,然而,我碰巧偶然发现了一个更好的实现这个功能的方法:

extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

例子

let array = [1, 2, 3]


for index in -20...20 {
if let item = array[safe: index] {
print(item)
}
}

适用于Swift 2

尽管这个问题已经被回答过很多次了,但我想给出一个更符合Swift编程时尚走向的答案,用Crusty的话来说就是:“首先考虑protocols”。

我们想做什么?< br > ——只有在给定索引是安全的情况下才获取__ABC0的元素,否则获取nil < br > •这个功能的实现应该基于什么?< br > ——__ABC0 subscripting < br > •这个功能从何而来?< br > ——它在__ABC1模块中定义的struct Array具有此属性 < br > •没有更一般/抽象的东西吗?< br > ——它采用protocol CollectionType来保证它 < br > •没有更一般/抽象的东西吗?< br > ——它也采用了protocol Indexable < br > 是的,听起来我们已经尽力了。然后我们可以扩展它以拥有我们想要的功能吗?< br > ——但是我们现在可以使用的类型(没有__ABC0)和属性(没有count)非常有限! < br > •这就足够了。Swift的stdlib做得很好;)

extension Indexable {
public subscript(safe safeIndex: Index) -> _Element? {
return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
}
}

不正确,但它给出了一个概念

  • 因为数组可以存储nil值,如果数组[index]调用越界,返回nil是没有意义的。
  • 因为我们不知道用户想如何处理越界问题,所以使用自定义操作符没有意义。
  • 相反,使用传统的控制流来展开对象并确保类型安全。

if let index = array.checkIndexForSafety(index:Int)

  let item = array[safeIndex: index]

if let index = array.checkIndexForSafety(index:Int)

  array[safeIndex: safeIndex] = myObject
extension Array {


@warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {


if indices.contains(index) {


// wrap index number in object, so can ensure type safety
return SafeIndex(indexNumber: index)


} else {
return nil
}
}


subscript(index:SafeIndex) -> Element {


get {
return self[index.indexNumber]
}


set {
self[index.indexNumber] = newValue
}
}


// second version of same subscript, but with different method signature, allowing user to highlight using safe index
subscript(safeIndex index:SafeIndex) -> Element {


get {
return self[index.indexNumber]
}


set {
self[index.indexNumber] = newValue
}
}


}


public class SafeIndex {


var indexNumber:Int


init(indexNumber:Int){
self.indexNumber = indexNumber
}
}

我发现安全数组获取,设置,插入,删除非常有用。我更倾向于记录并忽略错误,因为所有其他错误很快就会变得难以管理。完整代码如下

/**
Safe array get, set, insert and delete.
All action that would cause an error are ignored.
*/
extension Array {


/**
Removes element at index.
Action that would cause an error are ignored.
*/
mutating func remove(safeAt index: Index) {
guard index >= 0 && index < count else {
print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
return
}


remove(at: index)
}


/**
Inserts element at index.
Action that would cause an error are ignored.
*/
mutating func insert(_ element: Element, safeAt index: Index) {
guard index >= 0 && index <= count else {
print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
return
}


insert(element, at: index)
}


/**
Safe get set subscript.
Action that would cause an error are ignored.
*/
subscript (safe index: Index) -> Element? {
get {
return indices.contains(index) ? self[index] : nil
}
set {
remove(safeAt: index)


if let element = newValue {
insert(element, safeAt: index)
}
}
}
}

测试

import XCTest


class SafeArrayTest: XCTestCase {
func testRemove_Successful() {
var array = [1, 2, 3]


array.remove(safeAt: 1)


XCTAssert(array == [1, 3])
}


func testRemove_Failure() {
var array = [1, 2, 3]


array.remove(safeAt: 3)


XCTAssert(array == [1, 2, 3])
}


func testInsert_Successful() {
var array = [1, 2, 3]


array.insert(4, safeAt: 1)


XCTAssert(array == [1, 4, 2, 3])
}


func testInsert_Successful_AtEnd() {
var array = [1, 2, 3]


array.insert(4, safeAt: 3)


XCTAssert(array == [1, 2, 3, 4])
}


func testInsert_Failure() {
var array = [1, 2, 3]


array.insert(4, safeAt: 5)


XCTAssert(array == [1, 2, 3])
}


func testGet_Successful() {
var array = [1, 2, 3]


let element = array[safe: 1]


XCTAssert(element == 2)
}


func testGet_Failure() {
var array = [1, 2, 3]


let element = array[safe: 4]


XCTAssert(element == nil)
}


func testSet_Successful() {
var array = [1, 2, 3]


array[safe: 1] = 4


XCTAssert(array == [1, 4, 3])
}


func testSet_Successful_AtEnd() {
var array = [1, 2, 3]


array[safe: 3] = 4


XCTAssert(array == [1, 2, 3, 4])
}


func testSet_Failure() {
var array = [1, 2, 3]


array[safe: 4] = 4


XCTAssert(array == [1, 2, 3])
}
}
extension Array {
subscript (safe index: Index) -> Element? {
0 <= index && index < count ? self[index] : nil
}
}
  • O(1)的性能
  • 类型安全
  • 正确处理[MyType?)(返回MyType??,可以在两个级别上展开)
  • 不会导致set的问题吗
  • 简洁的代码

这是我为你做的一些测试:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int? (`b` contains a value and that value is `nil`)
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?

斯威夫特4

为那些更喜欢传统语法的人提供的扩展:

extension Array {
func item(at index: Int) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

基于Nikita Kukushkin的回答,有时候你需要安全地赋值给数组下标,也需要从它们中读取。

myArray[safe: badIndex] = newValue

因此,这里是对Nikita的答案(Swift 3.2)的更新,通过添加safe:参数名,也允许安全写入可变数组索引。

extension Collection {
/// Returns the element at the specified index if it is within bounds, otherwise nil.
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}


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
}
}
}
}
extension Array {
subscript (safe index: UInt) -> Element? {
return Int(index) < count ? self[Int(index)] : nil
}
}

使用上述提到的扩展返回nil,如果任何时候索引超出界限。

let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")

结果-无

我对数组做了一个简单的扩展

extension Array where Iterator.Element : AnyObject {
func iof (_ i : Int ) -> Iterator.Element? {
if self.count > i {
return self[i] as Iterator.Element
}
else {
return nil
}
}


}

它的设计完美无缺

例子

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode,

我知道这是个老问题了。我现在用的是Swift5.1, OP是swift1还是swift2 ?

今天我需要这样的东西,但是我不想仅仅为一个地方添加一个完整的扩展,而想要一些更实用的扩展(更线程安全?)我也不需要防止负标,只需要保护那些可能超过数组末尾的负标:

let fruit = ["Apple", "Banana", "Coconut"]


let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil

对于那些争论具有nil的序列的人,对于空集合返回nil的firstlast属性该如何处理?

我喜欢这个,因为我可以抓住现有的东西,用它来得到我想要的结果。我也知道dropFirst(n)不是一个完整的集合副本,只是一个切片。然后已经存在的第一个行为接管了我。

斯威夫特5用法

extension WKNavigationType {
var name : String {
get {
let names = ["linkAct","formSubm","backForw","reload","formRelo"]
return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
}
}
}

but 真的想做一般的喜欢

[<collection>][<index>] ?? <default>

但由于这个系列是有背景的,我想它是合适的。

“通常被拒绝的变更”;for Swift列表包含了改变数组下标访问,以返回可选而不是崩溃:

使Array<T>下标访问返回T?T!而不是T:当前数组行为是有意的,因为它准确地反映了越界数组访问是一个逻辑错误的事实。改变当前行为会使Array访问速度减慢到不可接受的程度。这个话题之前已经出现过多个次,但不太可能被接受。

https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

因此,基本下标访问不会更改为返回可选对象。

然而,Swift团队/社区似乎对添加开放,这是一种新的可选返回数组的访问模式,可以通过函数或下标。

这已经在Swift Evolution论坛上提出并讨论过了:

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871

值得注意的是,克里斯·拉特纳给这个想法加了一个“+1”:

同意,最常被建议的拼写是:yourArray[safe: idx],这对我来说似乎很棒。加上这个,我是+1。

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13

因此,在Swift的未来版本中,这可能是开箱即用的。我鼓励任何想要它的人加入Swift Evolution线程。

当你只需要从数组中得到值,并且你不介意一个小的性能损失(即如果你的集合不是很大),有一个基于dictionary的替代方案,它不涉及(对我来说太通用了)集合扩展:

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;

要传播操作失败的原因,错误比可选项更好。

public extension Collection {
/// Ensure an index is valid before accessing an element of the collection.
/// - Returns: The same as the unlabeled subscript, if an error is not thrown.
/// - Throws: `AnyCollection<Element>.IndexingError`
///   if `indices` does not contain `index`.
subscript(validating index: Index) -> Element {
get throws {
guard indices.contains(index)
else { throw AnyCollection<Element>.IndexingError() }
      

return self[index]
}
}
}


public extension AnyCollection {
/// Thrown when `[validating:]` is called with an invalid index.
struct IndexingError: Error { }
}
XCTAssertThrowsError(try ["🐾", "🥝"][validating: 2])


let collection = Array(1...10)
XCTAssertEqual(try collection[validating: 0], 1)
XCTAssertThrowsError(try collection[validating: collection.endIndex]) {
XCTAssert($0 is AnyCollection<Int>.IndexingError)
}

不知道为什么没有人,已经提出了一个扩展,也有一个setter自动增长数组

extension Array where Element: ExpressibleByNilLiteral {
public subscript(safe index: Int) -> Element? {
get {
guard index >= 0, index < endIndex else {
return nil
}


return self[index]
}


set(newValue) {
if index >= endIndex {
self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
}


self[index] = newValue ?? nil
}
}
}

使用很容易,工作在Swift 5.1

var arr:[String?] = ["A","B","C"]


print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]


arr[safe:10] = "Z"


print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]

注意:你应该理解在swift中增长数组时的性能成本(在时间/空间上)-但对于小问题,有时你只需要让swift停止自己的swift

你可以试试

if index >= 0 && index < array.count {
print(array[index])
}
< h1 id = " swift-5。x-skub“>斯威夫特5. x < / h1 >

RandomAccessCollection上的扩展意味着这也可以从单个实现用于ArraySlice。我们使用startIndexendIndex作为数组切片使用底层父Array的索引。

public extension RandomAccessCollection {


/// Returns the element at the specified index if it is within bounds, otherwise nil.
/// - complexity: O(1)
subscript (safe index: Index) -> Element? {
guard index >= startIndex, index < endIndex else {
return nil
}
return self[index]
}
    

}
说实话,我也遇到过这个问题。从性能的角度来看,Swift数组应该能够抛出。 让x = try a[y]

2022

无限索引访问和安全idx访问(如果没有这样的idex,则返回nil):

public extension Collection {
subscript (safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}


subscript (infinityIdx idx: Index) -> Element where Index == Int {
return self[ abs(idx) % self.count ]
}
}

但是要小心,如果数组/集合为空,它将抛出异常

使用

(0...10)[safe: 11] // nil


(0...10)[infinityIdx: 11] // 0
(0...10)[infinityIdx: 12] // 1
(0...10)[infinityIdx: 21] // 0
(0...10)[infinityIdx: 22] // 1