проблемы с настройкой на быстрый язык - PullRequest
0 голосов
/ 24 октября 2018

Я создал класс, который принимает протокол Hashable.

Поэтому я создал несколько экземпляров этого класса с различными свойствами и добавил их в набор.

Затем я изменяю свойствообъекта.

После этого изменения Set иногда терпит неудачу .contains (а также .remove).В инспекторе отладчика я вижу, что объект имеет тот же адрес памяти элемента внутри набора.Итак, почему терпеть неудачу наугад?Обратите внимание, что я всегда могу найти индекс элемента внутри набора.

Я тестировал с площадкой (на 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 голосов
/ 24 октября 2018

Вещи в наборе должны быть неизменными.

Вы никогда не должны помещать Test объекты в набор, потому что Test является полностью изменяемым.Именно поэтому вы получаете такое «странное и случайное» поведение.

Когда вы вызываете contains, набор (или, скорее, базовая хеш-таблица) оценивает хеш-код параметра и проверяет, есть ли хешКод соответствует любому из хеш-кодов в наборе.(Обратите внимание, что это упрощение, и оно звучит так, как будто это операция O (n). Это не так.)

Прежде чем изменить u2, он имеет хэш-код x.Набор помнит, что u2 имеет хэш-код х.Теперь вы меняете u2.Теперь он имеет другой хэш-код y.Таким образом, набор не может найти в нем элемент с хеш-кодом y.

Поэтому в основном вы должны убедиться, что все, что вы помещаете в набор, имеет постоянный хеш-код.

Вы можете сделать Test неизменным, выполнив:

let s: String

Если вы хотите узнать больше, вы можете посмотреть, как заданная структура данных реализована в Swift.Я нашел этот пост , который также может помочь.

0 голосов
/ 24 октября 2018

Из документов на NSSet:

Если изменяемые объекты хранятся в наборе, то либо метод хеширования объектов не должен зависеть от внутреннего состояния объекта.изменяемые объекты или изменяемые объекты не должны изменяться, пока они находятся в наборе.

Я думаю, что это точно охватывает этот случай.Правда, речь идет о 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.Несоответствие указывает на то, что вы не должны мутировать объект в наборе.

0 голосов
/ 24 октября 2018

Поскольку вы используете свойство 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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...