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

 圣换少爷 发布于 2022-12-28 06:17

如果我在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?
    }
}

Nikita Kukus.. 600

Alex的回答对这个问题有很好的建议和解决方案,但是,我偶然发现了一种更好的方法来实现这个功能:

Swift 3.2和更新版本

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

Swift 3.0和3.1

extension Collection where Indices.Iterator.Element == Index {

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

感谢Hamish 提出Swift 3的解决方案.

斯威夫特2

extension CollectionType {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.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)
    }
}

我认为这绝对值得关注 - 干得好.我喜欢包含的`safe:`参数名称以确保区别. (38认同)

从Swift 2(Xcode 7)开始,这需要一点调整:`return self.indices~ = index?self [index]:nil;` (11认同)

关于夫特3版本:可能的角的病例仅-提示,但提示仍然:存在这样的情况,其中"安全"的下标以上版本不是安全的(而夫特2版本是):用于`Collection`类型"指数"不是连续的.例如,对于`Set`实例,如果我们要通过索引(`SetIndex `)访问set元素,我们可以运行`= startIndex`和`(6认同)

嘿,我已经更新了Swift 3的答案.我会坚持使用Swift 2一段时间,所以如果有任何问题,请随意指出. (4认同)

原因是主观的.我把我的实现读作"数组的索引包括索引".它简洁,对我来说,似乎比通过比较进行的边界检查更简单.我也喜欢玩新的API.您可以自由地用您的实现替换我的实现,但是您可能还想添加对负索引的检查以完全"安全". (3认同)

警告!用这种方法检查数组可能非常昂贵。“包含”方法将遍历所有索引,从而使其成为O(n)。更好的方法是使用索引和计数来检查界限。 (3认同)

@JonColverson是的,即使对于极端情况也应该是安全的,但是注意到我们放松了`O(1)`随机索引访问(我们做了,但是,对于上面的'contains`Swift 2版本),这应该是'但是,除非在具有大型阵列的某些HPC应用程序中工作,否则这是一个问题.对于另一个替代方案(类似于"O(n)"),它对应于上面Swift 2版本的更直接的Swift 2-> 3翻译,请参阅[此问答中的答案](http://stackoverflow.com/ a/40331858/4573247)(使用`contains`也允许"短路",就像你明确的`while`循环解决方案一样). (2认同)


Alex Wayne.. 56

如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组.字典nil在访问丢失的密钥时返回,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中密钥必须在以下范围内:0to count.迭代这个范围是非常常见的,你可以绝对肯定在循环的每次迭代中都有一个真正的值.

我认为它不能以这种方式工作的原因是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?

8 个回答
  • Alex的回答对这个问题有很好的建议和解决方案,但是,我偶然发现了一种更好的方法来实现这个功能:

    Swift 3.2和更新版本

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

    Swift 3.0和3.1

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

    感谢Hamish 提出Swift 3的解决方案.

    斯威夫特2

    extension CollectionType {
    
        /// Returns the element at the specified index if it is within bounds, otherwise nil.
        subscript (safe index: Index) -> Generator.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)
        }
    }
    

    2022-12-28 06:37 回答
  • 在Swift 2中有效

    虽然已经有很多次回答了这个问题,但我想在Swift编程的时尚方面提出更多的答案,用Crusty的话来说就是:"先想想protocol"

    •我们想做什么?
    - 只有在安全的情况下才能获得给定索引的元素Array,nil否则
    •此功能应该什么为基础?
    - Array subscriptING
    •哪里不从得到这个功能吗?
    - struct ArraySwift模块中的定义有它
    •没有更通用/抽象的东西?
    - 它采用protocol CollectionType哪种方式确保它
    •没有更通用/抽象的东西?
    - 它也采用protocol Indexable......
    •是的,听起来像我们能做的最好.我们可以扩展它以获得我们想要的功能吗?
    - 但我们现在有非常有限的类型(没有Int)和属性(没有count)!
    •这就够了.Swift的stdlib做得很好;)

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

    ¹:不是真的,但它提出了这个想法

    2022-12-28 06:41 回答
  • 基于Nikita Kukushkin的答案,有时您需要安全地分配数组索引以及从它们读取,即

    myArray[safe: badIndex] = newValue
    

    所以这里是对Nikita的答案(Swift 3.2)的更新,它还允许通过添加safe:参数名称安全地写入可变数组索引.

    extension Collection {
        /// Returns the element at the specified index iff 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
                }
            }
        }
    }
    

    2022-12-28 06:43 回答
  • 因为数组可能存储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
        }
    }
    

    2022-12-28 06:47 回答
  • extension Array {
        subscript (safe index: Index) -> Element? {
            return 0 <= index && index < count ? self[index] : nil
        }
    }
    

    O(1)表现

    类型安全

    正确处理[MyType?]的Optionals(返回MyType ??,可以在两个级别解包)

    不会导致集合出现问题

    简洁的代码

    以下是我为你跑的一些测试:

    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?
    let c = itms[safe: 2] // nil : Int??
    c ?? 5 // 5 : Int?
    

    2022-12-28 06:51 回答
  • 如果你真的想要这种行为,它就像你想要一个Dictionary而不是一个数组.字典nil在访问丢失的密钥时返回,这是有道理的,因为要知道密钥是否存在于字典中要困难得多,因为这些密钥可以是任何东西,在数组中密钥必须在以下范围内:0to count.迭代这个范围是非常常见的,你可以绝对肯定在循环的每次迭代中都有一个真正的值.

    我认为它不能以这种方式工作的原因是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?

    2022-12-28 06:53 回答
  • 斯威夫特4

    对于那些喜欢更传统语法的人的扩展:

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

    2022-12-28 06:55 回答
  • 我发现安全的数组get,set,insert,remove非常有用。我更喜欢记录日志并忽略错误,因为其他所有问题很快都会变得难以管理。完整代码如下

    /**
     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])
        }
    }
    

    2022-12-28 06:59 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有