快速语言设置的问题

时间:2018-10-24 12:26:43

标签: swift xcode set

我创建了一个采用Hashable协议的类。

所以我创建了具有不同属性的此类的某些实例,并将它们添加到Set中。

然后我更改对象的属性。

此更改之后,集合有时会失败.contains(也可能失败.remove)。 在调试器检查器中,我看到该对象与Set中的元素具有相同的内存地址。那么,为什么随机失败呢? 请注意,我总能在集合中找到元素的索引。

我在(xcode10上)操场上进行了多次测试,每次执行时结果都会改变。

class Test: Hashable {
    // MARK: Equatable protocol
    static func == (lhs: Test, rhs: Test) -> Bool {
        return lhs === rhs || lhs.hashValue == rhs.hashValue
    }

    var s: String
    func hash(into hasher: inout Hasher) {
        hasher.combine(s.hashValue)

    }
    init(s: String) {
        self.s = s
    }
}

func test(s: Set<Test>, u: Test) -> Bool {
    if s.contains(u) {
        print("OK")
        return true
    } else {
        print("FAIL") // SOMETIMES FAIL
        if !s.contains(u) {
            if let _ = s.firstIndex(where: { $0 == u }) {
                print("- OK index") // ALWAYS OK
                return true
            } else {
                print("- FAIL index") // NEVER FAIL
                return false
            }
        } else {
            return true
        }
    }
}

var s: Set<Test> = []
let u1 = Test(s: "a")
s.insert(u1)
let u2 = Test(s: "b")
s.insert(u2)
test(s: s, u: u2)

u2.s = "c"
test(s: s, u: u2)
u2.s = "d"
test(s: s, u: u2)
u2.s = "b"
test(s: s, u: u2)

3 个答案:

答案 0 :(得分:2)

集合中的内容应该是不变的。

您永远不应该将Test对象放入集合,因为Test是完全可变的。这就是为什么您会获得这些“奇怪且随机的”行为的原因。

调用contains时,集合(或更确切地说是基础哈希表)将评估参数的哈希码,并查看哈希码是否与集合中的任何哈希码匹配。 (请注意,这是一个过分的简化,听起来像是O(n)操作。不是。)

在更改u2之前,它的哈希码为x。该集合记住u2的哈希码为x。现在,您更改u2。现在,它具有不同的哈希码y。因此,该集合无法在其中找到哈希值为y的元素。

因此,基本上,您应该确保放入集合中的所有内容都具有恒定的哈希码。

您可以通过以下操作使Test不可变:

let s: String

如果您想了解更多信息,可以查看如何在Swift中实现集合数据结构。我发现这个post可能也有帮助。

答案 1 :(得分:2)

从NSSet上的docs

  

如果可变对象存储在集合中,则对象的哈希方法不应该依赖于可变对象的内部状态,或者当可变对象在集合中时不应对其进行修改。

>

我认为这完全涵盖了这种情况。没错,这是关于Cocoa NSSet的,但是我希望Swift Set在这方面与NSSet相对应。

仅作记录,我能够重现您描述的行为,从而消除了一些更卑鄙或更可疑的代码-不在操场上,通过==的合法实现并使用hashValue ,而无需不必要地调用test函数:

class Test: Equatable, Hashable, CustomStringConvertible {
    var s: String
    static func == (lhs: Test, rhs: Test) -> Bool {
        return lhs.s == rhs.s
    }
    var hashValue: Int {
        return s.hashValue
    }
    init(s: String) {
        self.s = s
    }
    var description: String {
        return "Test(\(s))"
    }
}

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        var s: Set<Test> = []
        let u1 = Test(s: "a")
        s.insert(u1)
        let u2 = Test(s: "b")
        s.insert(u2)

        print(s.contains(u2))
        u2.s = "c"
        print(s.contains(u2))
    }
}

要对其进行测试,请反复运行该项目。有时您会得到true true,有时会得到true false。不一致表示您不应该对集合中的对象进行变异。

答案 2 :(得分:-1)

由于您正在使用s类的Test属性来创建哈希值,因此请尝试比较s值而不是比较对象,即

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

这将解决您的问题。另外,正如我在评论中提到的那样,在失败情况下无需使用额外的if-else。您可以简单地使用以下代码:

func test(s: Set<Test>, u: Test) -> Bool
{
    if s.contains(u)
    {
        print("OK")
        return true
    }
    else
    {
        print("FAIL: \(u.s)") // SOMETIMES FAIL
        return false
    }
}