swift 从Set数据结构中删除和删除成员是否安全?

wlwcrazw  于 12个月前  发布在  Swift
关注(0)|答案(4)|浏览(88)

我知道,从Array数据结构中删除和删除成员是不安全的。

var datas = ["X", "Y", "Z", "1", "2", "3"]

for (index, data) in datas.enumerated() {
    if data == "Y" || data == "Z" || data == "3" {
        datas.remove(at: index)    // Crash!
    }
}

字符串
但是,Set数据结构呢?我在运行以下代码时没有遇到运行时错误。

var datas = Set(["X", "Y", "Z", "1", "2", "3"])

for data in datas {
    if data == "Y" || data == "Z" || data == "3" {
        datas.remove(data)    // No crash.
    }
}


但这样做安全吗?会出现什么问题呢?

mklgxw1f

mklgxw1f1#

迭代任何序列(包括数组和集合)都可以使用一个名为next()的函数。在next()值可用之前,您可以进行任何操作。因此,**可以从正在迭代的数组中删除元素。
不安全的部分是调用index!,因为从数组中删除一个项可能会导致索引的更改。
尽管你可以小心地执行安全的基于索引的操作:

var datas = ["X", "Y", "Z", "1", "2", "3"]

for data in datas { // 👈 Not iterating over indices of the array 
    if data == "Y" || data == "Z" || data == "3" {
        guard let index = datas.firstIndex(of: data) else { continue }
        datas.remove(at: index) // 👈 Using the index 
    }
}

字符串

回顾

***在迭代过程中,修改任何序列的元素(包括ArraySet)都是安全的。

  • 在迭代过程中发生更改时,假设索引并直接访问序列是不安全
lmyy7pcs

lmyy7pcs2#

这是关于数据结构的。
1.数组结构是一个有序的集合,允许你通过位置(索引)来访问它的元素。在缺少索引的地方访问会导致致命错误。

  1. Set结构是一个未排序的集合,你可以通过key访问它的元素。它是一个幕后的哈希表。这就是为什么所有的元素都应该符合Hashable协议。当获取/删除一个元素时,Set首先用hash函数从哈希表中查找,然后返回值,如果它存在。有时会有共谋,但是Set结构中的remove函数返回一个可选值,所以它根本不会崩溃。
8fq7wneg

8fq7wneg3#

这两个版本的代码并没有真正的可比性。你在这里有点像在比较苹果和橘子。
在数组的代码中,你正在使用索引 * 删除元素。当你删除一个元素时,数组中其他元素的索引可能会改变,一些曾经有效的索引可能会变得无效。循环最终到达“3”,它在原始数组中的索引为5,但现在不再有效,因为数组现在只有4个元素。
注意数组代码中的崩溃并不是由于在迭代数组时从数组中删除元素而引起的。崩溃仅仅是因为你使用了无效的索引。正如你将在后面看到的,如果你不使用索引,在迭代数组时从数组中删除元素是完全可以的。
在sets的代码中,没有索引。你只是使用了Set.remove(_ member: Element)方法。你永远不能传递一个“无效”的元素给这个方法-要么元素存在于set中,它将被删除,要么元素不存在,什么也不会发生。所以回答你的问题:你的sets代码是安全的。
为了实际比较数组和集合的行为,你应该使用一个索引来从集合中删除一个元素:

var datas = Set(["X", "Y", "Z", "1", "2", "3"])

for index in datas.indices {
    let data = datas[index]
    if data == "Y" || data == "Z" || data == "3" {
        datas.remove(at: index)
    }
}

字符串
现在代码崩溃了(虽然我不确定这是否会 * 总是 * 发生)。
或者,你也可以在从数组中删除时不使用索引:

var datas = ["X", "Y", "Z", "1", "2", "3"]

for data in datas {
    if data == "Y" || data == "Z" || data == "3" {
        // removeAll { $0 == data } has similar semantics to Set.remove
        datas.removeAll { $0 == data }
    }
}


这段代码不会崩溃。

  • enumerated返回的元组序列中的第一个元素实际上并不是该元素在集合中的索引。它只是一个从0到-1的数字。碰巧它与Array的每个元素的索引相同。
xzlaal3s

xzlaal3s4#

在这两种情况下,手动使用for循环进行迭代都不是好方法。
对于Array,您可以使用简单的removeAll(where:)

var data = ["X", "Y", "Z", "1", "2", "3"]
data.removeAll { data == "Y" || data == "Z" || data == "3" }

字符串
对于Set,你也可以使用removeAll,但是集合减法(例如subtracting(_:),或者与subtract(_:)一起使用)会更习惯:

var data: Set = ["X", "Y", "Z", "1", "2", "3"]
data.subtract(["Y", "Z", "3"])

相关问题