Swift 可变集:找到重复元素

Swift mutable set: Duplicate element found

我的应用使用可变的自定义元素集。有一次我因错误“在 Set 中发现重复元素”而崩溃。元素可能在插入后发生了变化。“

搜索解释,我发现this post,我不太明白。
我的印象是不应该修改集合的元素,因为这也会修改集合的哈希值,因此进一步的访问可能会失败。

我的问题:

编辑:

换句话说:修改可变集合的自定义元素的 属性 而不修改集合本身安全吗?

允许的操作: 您可以从 NSMutableSet 添加、删除和更新元素。如果你想 update/add 一个元素,那么你必须调用 .add() 方法,它会将给定的对象添加到集合中,如果它还不是成员的话。

请查看有关 NSMutableSet 的 here Apple 文档。

您可以执行所有类型的操作,例如添加、删除、更新等。

Swift 集合的实现类似于字典的实现,Exploring Swift Dictionary's Implementation 中对其进行了很好的描述。特别地,元素存储是一个“桶”的列表,每个桶都可以被占用或不被占用。当一个新元素被插入到集合中时,它的散列值被用来确定初始桶。如果该桶被占用,则对下一个空闲桶进行线性搜索。同理,在集合中查找元素时,通过哈希值来确定初始桶,然后进行线性查找,直到找到元素(或未被占用的桶)。

(具体可以参考开源实现,最相关的源文件是 Set.swift, NativeSet.swift, SetStorage.swift and HashTable.swift.)

改变插入元素的哈希值会破坏集合存储实现的不变量:通过其初始存储桶定位元素不再有效。改变影响相等性的其他属性可能会导致同一存储桶列表中出现多个“相等”元素。

因此我认为可以这么说

After inserting an instance of a reference type into a set, the properties of that instance must not be modified in a way that affects its hash value or testing for equality.

例子

首先,这只是 引用类型的集合的问题。 值类型的集合包含值的独立副本,并且修改 属性插入后的那个值不影响集合:

struct Foo: Hashable {
    var x: Int
}

var set = Set<Foo>()
var foo = Foo(x: 1)
set.insert(foo)
print(set.map { [=10=].x })   // [1]
foo.x = 2
print(set.map { [=10=].x })   // [1]
set.insert(foo)
print(set.map { [=10=].x })   // [1, 2]

引用类型的实例是指向实际对象存储的“指针”,修改该实例的 属性 不会改变引用。因此可以在实例插入集合后修改实例的 属性:

class Bar: Hashable {
    var x : Int

    init(x: Int) { self.x = x }

    static func == (lhs: Bar, rhs: Bar) -> Bool { return lhs.x == rhs.x }

    func hash(into hasher: inout Hasher) { hasher.combine(x) }
}

var set = Set<Bar>()
let bar = Bar(x: 1)
set.insert(bar)
print(set.map { [=11=].x })   // [1]
bar.x = 2
print(set.map { [=11=].x })   // [2]

但是,这很容易导致崩溃,例如如果我们再次插入相同的引用:

set.insert(bar)
Fatal error: Duplicate elements of type 'Bar' were found in a Set.
This usually means either that the type violates Hashable's requirements, or
that members of such a set were mutated after insertion.

这是另一个示例,其中所有实例的哈希值都相同,但修改用于相等性测试的 属性 会导致一组两个“相等”实例:

class Baz: Hashable {
    var x : Int

    init(x: Int) { self.x = x }

    static func == (lhs: Baz, rhs: Baz) -> Bool { return lhs.x == rhs.x }

    func hash(into hasher: inout Hasher) { }
}

var set = Set<Baz>()
let baz1 = Baz(x: 1)
set.insert(baz1)
let baz2 = Baz(x: 2)
set.insert(baz2)
baz1.x = 2

print(set.map { [=13=].x })   // [2, 2]
print(set.count)             // 2
print(Set(Array(set)).count) // 1 

基本上 swift stdlib 是一种(经常)无法使用的垃圾(类似于 C++ STL 和其他 C++ 垃圾技术)。 NextStep 传统中开箱即用的东西 天(如果有的话)在 swift 运行时(例如 Set)中正确完成。

我花了大约两个工作日来寻找 faux 哈希和 == 不匹配 放弃 swift 设置为支持 NSMutableSet 并且不知何故我的应用程序现在非常好。

我有没有提到 swift 运行时是垃圾?

这个:

public extension NSSet
{
    var isEmpty: Bool {
        return count == 0
    }
}
public extension NSMutableSet
{
    func insert(_ item: Any)
    {
        add(item)
    }
}

将帮助您无缝转换