在 Swift 中为 Collection 下标中的 Index 创建变异集

Create mutating set for Index in Collection subscript in Swift

我正在尝试修改此扩展以获得可变性:

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

我想实现这个工作:

var array = ["Hello", "World"]
array[safe: 5] = "Other word" // This will not be setted because index is out of bounds, but error won't be throwed.

当我尝试修改下标扩展时...

extension Collection where Element: Equatable {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index) {
                self[index] = newValue        <<<-- ERROR
            }
        }
    }
}

...我收到此错误:Missing argument label 'safe:' in subscript

问题是 Collection 没有可变下标 setter。为此,您需要改为扩展 MutableCollection

extension MutableCollection where Element: Equatable {
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript(safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self[index] = value
            }
        }
    }
}

Collectionsubscript 是只获取的。

MutableCollection is the protocol that Array conforms to, that declares the settable subscript 我们都知道并喜爱。毕竟,您的扩展程序要求集合是可变的,并且并非所有 Collection 都是可变的,只有 MutableCollection 是可变的。

extension MutableCollection {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self[index] = value
            }
        }
    }
}

请注意,我还在 setter 中为 newValue == nil 的情况添加了检查(因为下标的类型为 Element?,您可以将 nil 分配给它!)。在那种情况下,下标不会做任何事情。也不需要 Element : Equatable 约束。


从技术上讲,这也可以在 RangeReplaceableCollections 上完成,这是 Array 遵守的另一种协议。但是下标应该是O(1)时间,而replaceSubrange不一定如此。

extension RangeReplaceableCollection {
    
    /// Returns the element at the specified index iff it is within count, otherwise nil.
    subscript (safe index: Index) -> Element? {
        get {
            indices.contains(index) ? self[index] : nil
        }
        mutating set {
            if indices.contains(index), let value = newValue {
                self.replaceSubrange(index...index), with: CollectionOfOne(value))
            }
        }
    }
}