如何定义自定义下标数组运算符,以便在必要时"spring into existence"数组元素



是否可以将运算符func添加到Swift类下标方法中

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,则返回默认值b但是它返回一个非可选值。这就是??的要点。

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

PS??=函数编译的原因是,如果需要,非可选值(即您将从lhs ?? rhs返回的值)会隐式升级为可选值,因此类型为Tlhs ?? rhs可以分配给类型为T?lhs

如果您不要求此"扩展下标"操作在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,将下标转发到底层字典,等等)。

由于@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]

(你甚至可以想象一下,做一个对映射索引的默认值使用闭包的版本)。

如果我可以加入派对:)喜欢rickster空速的回应。。。正在扩展序列类型和默认值类型。。。

问题的提出方式本质上描述了有序集行为(仅由运算符而非类型提供)。因此,我们可以用一些非常简单的方式来处理这个想法:

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

然而,我们可以寻求保留苹果与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($0) } // 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"]

最新更新