Быстрый изменяемый набор: найден повторяющийся элемент - PullRequest
3 голосов
/ 14 апреля 2019

Мое приложение использует изменяемые наборы пользовательских элементов.Однажды у меня произошел сбой с ошибкой «Дублированный элемент найден в Set.Элементы могли быть видоизменены после вставки. «

В поисках объяснения я нашел этот пост , который я не до конца понимаю.
У меня сложилось впечатление, что не следует изменятьэлемент набора, так как это также изменило бы значение хеша набора, так что дальнейший доступ может потерпеть неудачу.

Мои вопросы:

  • Разрешено ли изменять элемент изменяемого набора или какие изменения разрешены, если они есть?
  • Если нет, могу ли янужно сначала удалить элемент из набора, затем изменить его, а затем вставить обратно?

РЕДАКТИРОВАТЬ:

Чтобы сформулировать это по-другому: Безопасно ли изменять свойствопользовательский элемент изменяемого набора, без изменения самого набора?

Ответы [ 2 ]

5 голосов
/ 14 апреля 2019

Реализация наборов Swift аналогична реализации словарей, которая хорошо описана в Изучение реализации Swift Dictionary .В частности, хранилище элементов представляет собой список «блоков», каждое из которых может быть занято или нет.Когда новый элемент вставляется в набор, его хэш-значение используется для определения начального сегмента.Если этот сегмент занят, выполняется линейный поиск следующего свободного сегмента.Аналогично, при поиске элемента в наборе значение хеш-функции используется для определения начального сегмента, а затем выполняется линейный поиск до тех пор, пока не будет найден элемент (или незанятый сегмент).

(подробности могутв реализации с открытым исходным кодом наиболее подходящими исходными файлами являются 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 голосов
/ 14 апреля 2019

Разрешенные операции: Вы можете добавлять, удалять, обновлять элементы из NSMutableSet. Если вы хотите обновить / добавить элемент, вам нужно вызвать метод .add(), он добавит данный объект в набор, если он еще не является членом.

Пожалуйста, проверьте здесь Документация Apple относительно NSMutableSet.

Вы можете выполнять все типы операций, такие как добавление, удаление, обновление и т. Д.

...