快速可变集合:发现重复元素

时间:2019-04-14 10:24:19

标签: swift set mutable

我的应用使用可变的自定义元素集。一旦我崩溃了,并发生了错误“在Set中发现重复元素”。插入后元素可能已经突变。”

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

我的问题:

  • 是否允许修改可变集合的元素,或者允许进行任何修改?
  • 如果不是,我是否必须首先从集合中删除该元素,然后对其进行修改,然后再将其重新插入?

编辑:

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

3 个答案:

答案 0 :(得分:3)

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

(详细信息可以在开源实现中找到,最相关的源文件是 Set.swiftNativeSet.swiftSetStorage.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 

答案 1 :(得分:0)

允许的操作:您可以添加,删除和更新NSMutableSet中的元素。如果要更新/添加元素,则必须调用.add()方法,如果该方法尚不是成员,它将向集合中添加给定对象。

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

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

答案 2 :(得分:-1)

基本上快速的stdlib是一种(经常)不可用的垃圾(类似于C ++ STL和其他c ++垃圾技术)。 NextStep传统中开箱即用的功能 天(如果有)在快速运行时(例如Set)将其正确设置。

我花了大约两个工作日来追踪人造哈希和==不匹配 放弃了Swift Set,转而使用NSMutableSet,现在我的应用程序还可以。

我提到快速运行时是垃圾吗?

此:

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

将帮助您无缝转换