+-
Swift可变集:找到重复元素

我的应用程序使用可变的自定义元素集。一旦我遇到错误“在Set中找到重复元素。插入后元素可能已经发生变异。“

寻找解释,我发现this post,我不完全理解。 我的印象是,不应该修改集合的元素,因为这也会修改集合的散列值,因此进一步的访问可能会失败。

我的问题:

是否允许修改可变集的元素,或允许哪些修改? 如果没有,我是否首先从集合中删除元素,然后修改它,然后将其插回?

编辑:

用不同的方式表达它:修改可变集的自定义元素的属性是否安全,而不修改集合本身?

5
投票

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

(详细信息可以在开源实现中找到,最相关的源文件是Set.swift,NativeSet.swift,SetStorage.swift和HashTable.swift。)

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

因此,我认为这样说是安全的

在将引用类型的实例插入集合后,不得以影响其哈希值或测试相等性的方式修改该实例的属性。

例子

首先,这只是参考类型集的问题。一组值类型包含值的独立副本,并且在插入后修改该值的属性不会影响该集:

struct Foo: Hashable {
    var x: Int
}

var set = Set<Foo>()
var foo = Foo(x: 1)
set.insert(foo)
print(set.map { $0.x })   // [1]
foo.x = 2
print(set.map { $0.x })   // [1]
set.insert(foo)
print(set.map { $0.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 { $0.x })   // [1]
bar.x = 2
print(set.map { $0.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 { $0.x })   // [2, 2]
print(set.count)             // 2
print(Set(Array(set)).count) // 1 😲
0
投票

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

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

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