Swift 可变集:找到重复的元素

Swift mutable set: Duplicate element found

提问人:Reinhard Männer 提问时间:4/14/2019 最后编辑:Reinhard Männer 更新时间:2/24/2022 访问量:6237

问:

我的应用使用可变的自定义元素集。有一次我崩溃了,出现错误“在 Set 中找到重复的元素。元素可能在插入后发生了突变。

在寻找解释时,我找到了这篇文章,我不完全理解。
我的印象是,不应该修改集合的元素,因为这也会修改集合的哈希值,因此进一步的访问可能会失败。

我的问题:

  • 是否允许修改可变集的元素,或者允许哪些修改(如果有)?
  • 如果没有,我是否必须先从集合中删除元素,然后修改它,然后重新插入它?

编辑:

换个说法:在不修改集合本身的情况下修改可变集的自定义元素的属性是否安全?

swift 设置为 可变

评论

0赞 Muhammad Waqas Bhati 4/14/2019
您的自定义对象类是否正在确认 Hasable 协议?如果是这样,那么您能否向我们展示代码崩溃的位置
0赞 Reinhard Männer 4/14/2019
根据文档,Swift 集必须符合 Hashable 协议,所以我的自定义元素符合。 实际上,我前段时间通过变通方法消除了崩溃,并留下了一个 TODO 来澄清情况。所以我无法显示崩溃的代码。
0赞 Reinhard Männer 4/14/2019
为什么这个问题被否决了?
0赞 Muhammad Waqas Bhati 4/14/2019
好的,知道了......仅当您的自定义对象不符合 Hashable 协议时,duplicate 才会出现。
3赞 Martin R 4/14/2019
我没有引用,但我确信在插入到集合中后,您不得以任何影响哈希值或相等性测试的方式修改实例。

答:

1赞 Muhammad Waqas Bhati 4/14/2019 #1

允许的操作:您可以从 中添加、删除、更新元素。如果要更新/添加元素,则必须调用 method,如果它还不是成员,它会将给定对象添加到集合中。NSMutableSet.add()

请在此处查看有关 NSMutableSet 的 Apple 文档。

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

评论

0赞 Reinhard Männer 4/14/2019
对不起,我没有把我的问题分阶段足够清楚:我想知道,我是否可以在不改变集合本身的情况下修改集合元素的属性。
22赞 Martin R 4/15/2019 #2

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

(详细信息可以在开源实现中找到,最相关的源文件是 Set.swift、NativeSet.swift、SetStorage.swiftHashTable.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 😲
-5赞 Anton Tropashko 2/12/2020 #3

基本上,swift stdlib 是一种(通常)不可用的垃圾(类似于 C++ STL 和其他 c++ 垃圾技术)。NextStep 旧版中开箱即用的东西需要 天(如果有的话)在 swift 运行时(例如 Set)中正确处理它。

我花了大约两个工作日的时间来追逐虚假哈希和 == 不匹配 抛弃了 swift Set,转而使用 NSMutableSet,不知何故,我的应用程序现在非常好。

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

这:

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

将帮助您无缝转换