如何定义一个自定义的下标数组运算符,它在必要时使数组元素 "spring into existence"

how to define a custom subscripting array operator which makes array elements "spring into existence" if necessary

是否可以将运算符函数添加到 Swift class 下标方法

var x = ["dkfkd", "dkff"]
x[2] ??=  "mmmm" // equal to x[2] = x[2] ?? "mmmm"

这与下标运算符无关,更多的是如何定义??=运算符的问题。你可以做到,但它可能不会像你期望的那样工作。

这是一个可能的实现:

// first define the ??= operator
infix operator ??= { }

// then some pretty standard logic for an assignment
// version of ??
func ??=<T>(inout lhs: T?, rhs: T) {
    lhs = lhs ?? rhs
}

这可以编译,并且可以像您可能期望的那样工作:

var i: Int? = 1

i ??= 2   // i unchanged

var j: Int? = nil

j ??= 2  // j is now Some(2)

它也可以与下标结合使用:

var a: [Int?] = [1, nil]

a[1] ??= 2

a[1]  // is now Some(2)

我说由于类型的原因,这可能无法完全按预期工作。 a ?? b 采用可选的 a,如果是 nil,returns 默认为 b但是它returns是一个非可选值。这就是 ??.

的重点

另一方面,??=不能这样做。因为左侧已经确定为可选,赋值运算符不能仅更改值的类型。因此,虽然它会在 nil 的情况下替换可选内部的值,但不会将类型更改为非可选。

PS ??= 函数编译的原因是因为非可选值(即你将从 lhs ?? rhs 返回的值)在必要时隐式升级为可选值,因此 lhs ?? rhs,类型 T,可以分配给 lhs,类型 T?

由于@MartinR 指出我可能误解了这个问题,这里是对其他可能解释的回答——您想要一个下标,在集合不存在时向其添加一个值。

您无法通过接线员完成此操作。而且您也无法更改现有集合上标准下标运算符的行为。但是您 可以 添加一个新的下标运算符,它采用自定义类型。所以你可以试试这个:

// custom type that is used to indicate behaviour
struct IfAbsent<I> {
    let idx: I
    init(_ idx: I) { self.idx = idx }
}

// add an array extension that defines a subscript that takes an IfAbsent type
extension Array {
    subscript(idx: IfAbsent<Int>) -> T{
        get {
            return self[idx.idx]
        }
        set(newValue) {
            if idx.idx < self.count {
                self[idx] = newValue
            }
            else {
                self.extend(Repeat(count: idx.idx - self.count + 1, repeatedValue: newValue))
            }
        }
    }
}
var x = ["dkfkd", "dkff"]
x[IfAbsent(1)] = "does nothing"
x[IfAbsent(2)] = "will insert this”
// x will be equal to ["dkfkd", "dkff", "will insert this"]

缺点是数组在每个位置都有值,所以你必须把这个新值放在当前最后一个和目标下标之间的每个条目中:

var x = [1,2,3]
x[10] = 1
// x is now [1, 2, 3, 1, 1, 1]

字典的效果更好:

extension Dictionary {
    subscript(idx: IfAbsent<Key>) -> Value? {
        get {
            return self[idx.idx]
        }
        set(newValue) {
            if self[idx.idx] == nil {
                self[idx.idx] = newValue
            }
        }
    }
}

你也可以做一个默认填充数组的版本,与在特定下标处分配的值分开:

struct WithDefault<T> {
    let idx: Int
    let dflt: T
    init(_ idx: Int, _ dflt: T) {
        self.idx = idx
        self.dflt = dflt
    }
}
extension Array {
    subscript(idx: WithDefault<T>) -> T {
        get {
            return idx.idx < self.count
                ? self[idx.idx]
                : idx.dflt
        }
        set(newValue) {
            if idx.idx < self.count {
                self[idx] = newValue
            }
            else {
                self.extend(Repeat(count: idx.idx - self.count, repeatedValue: idx.dflt))
                self.append(newValue)
            }
        }
    }
}

这也有下标 get 的合理默认值的好处:

var x = [1,2,3]
x[WithDefault(5, 99)]  // returns 99
x[WithDefault(5, 0)] = 5
// x is now [1, 2, 3, 0, 0, 5]

(您甚至可以想象并做一个为映射索引的默认值采用闭包的版本)。

如果您不要求此 "extending subscript" 操作在 Array 本身上,您可能会发现创建自己的类型来包装 Array(或其他缓冲区)而不是将此行为楔入 Array 扩展。

首先,您可能只是在考虑使用 array[array.count] = item 作为附加的同义词,对吗? (除此之外,它变得更加复杂,正如@AirspeedVelocity 指出的那样,因为存在在现有数组元素和新项目之间放置什么的问题。)这是一个做到这一点的版本:

struct ExtendingArray<T>: ArrayLiteralConvertible {
    typealias Element = T
    var array: [T] = []

    init(arrayLiteral elements: Element...) {
        self.array = elements
    }

    subscript(index: Int) -> T {
        get {
            return array[index]
        }
        set(newValue) {
            if index < array.count {
                array[index] = newValue
            } else if index == array.count {
                array.append(newValue)
            } else {
                fatalError("can't assign subscript beyond append range")
            }
        }
    }
}

// playground example
var foo: ExtendingArray = ["a", "b"]
foo[2] = "c"
foo // {["a", "b", "c"]}

如果您希望能够创建一个稀疏集合,您可以在其中分配任何数字索引处的元素,而无需在非连续索引之间填充占位符元素。 . 你实际上并不想要一个 Array ——你想要一个 Dictionary ,它的键是整数。如果你愿意,你可以只用 Dictionary 来做到这一点:

var sparse = [Int:String]()
sparse[0] = "a"
sparse[1] = "b"
sparse[26] = "z"
sparse // [0: "a", 1: "b", 26: "z"]

或者,如果你想要稍微更像数组的语义,你可以创建自己的类型来包装 Dictionary(并采用 ArrayLiteralConvertible,将下标转发到底层字典等)。

如果我可以参加派对 :) 喜欢 ricksterAirspeed Velocity 的回复...扩展序列类型和默认值类型 ...

问题的提出方式实质上描述了一个有序集合的行为(仅由运算符而不是类型提供)。所以我们可以用一些非常直接的方式来实践这个想法:

infix operator ??= { }

func ??= <T: Hashable> (inout lhs: [T], rhs: T) {
    if Set(lhs).contains(rhs) {
        lhs += [rhs]
    }
}

但是,我们可以寻求保留 Apple 使用 Swift 的句法方向,并在其 += 数组连接运算符的基础上进行构建:

infix operator +== { }

func +== <T: Hashable> (inout lhs: [T], rhs: [T]) {
    lhs += Set(rhs).subtract(lhs) 
}

但是,这并没有保留 rhs 中不在 lhs 中的元素的相对顺序。为此,我们可以:

func +== <T: Hashable> (inout lhs: [T], rhs: [T]) {
    let dif = Set(rhs).subtract(lhs)
    lhs += filter(rhs) { dif.contains([=12=]) } // preserves order (fun version - not efficient!)
}

可以这样使用:

var x = ["dkfkd", "dkff"]
x +== ["mmmm"]                              //--> ["dkfkd", "dkff", "mmmm"]
x +== ["and", "dkff", "so", "dkfkd", "on"]  //--> ["dkfkd", "dkff", "mmmm", "and", "so", "on"]