Пользовательская семантика равенства
Реализуйте правила равенства, соответствующие предметной области.
«Пользовательская семантика равенства» — бесплатный урок Swift Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.
Когда стандартный == даёт неверный результат
Синтезированный == сравнивает каждое хранимое свойство. Иногда это слишком строго (Вас интересует только идентификатор) или, наоборот, слишком нестрого. В таком случае напишите == самостоятельно.
Равенство по идентификатору
Считайте две записи равными, если их идентификаторы совпадают, игнорируя остальные поля:
struct Account: Equatable {
let id: Int
var balance: Int
static func == (l: Account, r: Account) -> Bool { l.id == r.id }
}
print(Account(id: 1, balance: 10) == Account(id: 1, balance: 999)) // trueРавенство без учёта регистра
Перед сравнением приведите значения к нормализованному виду:
struct Username: Equatable {
let raw: String
static func == (l: Username, r: Username) -> Bool {
l.raw.lowercased() == r.raw.lowercased()
}
}
print(Username(raw: "Ann") == Username(raw: "ANN")) // trueСогласованность хешируемости
Если Вы настраиваете ==, MUST обновить hash(into:), чтобы он использовал те же поля, иначе равные значения могут получить разные хеши и нарушить работу Set.
Согласование == и hash(into:)
Оба механизма используют только id, поэтому остаются согласованными:
struct User: Hashable {
let id: Int
var name: String
static func == (l: User, r: User) -> Bool { l.id == r.id }
func hash(into h: inout Hasher) { h.combine(id) }
}
print(Set([User(id: 1, name: "A"), User(id: 1, name: "B")]).count) // 1Равенство без учёта кэша
Исключите производные или кэшированные поля из сравнения:
struct Doc: Equatable {
let text: String
var cachedWordCount: Int
static func == (l: Doc, r: Doc) -> Bool { l.text == r.text }
}
let a = Doc(text: "hi", cachedWordCount: 1)
let b = Doc(text: "hi", cachedWordCount: 999)
print(a == b) // trueРавенство чисел с плавающей точкой с допуском
Сравнивайте числа double с заданной эпсилон-точностью, а не на точное совпадение:
struct Measure: Equatable {
let value: Double
static func == (l: Measure, r: Measure) -> Bool {
abs(l.value - r.value) < 0.0001
}
}
print(Measure(value: 0.1 + 0.2) == Measure(value: 0.3)) // trueРавенство по подмножеству полей
Для предметного равенства сравнивайте значимое подмножество полей:
struct Card: Equatable {
let rank: Int
let suit: String
let isFaceUp: Bool // display state, not identity
static func == (l: Card, r: Card) -> Bool {
l.rank == r.rank && l.suit == r.suit
}
}
print(Card(rank: 1, suit: "H", isFaceUp: true) == Card(rank: 1, suit: "H", isFaceUp: false)) // trueСоблюдайте законы Equatable
Пользовательский == должен оставаться рефлексивным (a == a), симметричным (из a == b следует b == a) и транзитивным. Нарушение этих свойств приводит к непредсказуемому поведению коллекций и алгоритмов.
Симметричная реализация
Сравнение нормализованных форм сохраняет симметрию:
struct Email: Equatable {
let address: String
static func == (l: Email, r: Email) -> Bool {
l.address.lowercased() == r.address.lowercased()
}
}
let x = Email(address: "A@x.com")
let y = Email(address: "a@X.com")
print(x == y, y == x) // true trueПользовательское равенство в множестве
Поскольку равенство и хеширование только по идентификатору согласованы, Set считает значения с одним идентификатором одним элементом:
struct Item: Hashable {
let id: Int
var note: String
static func == (l: Item, r: Item) -> Bool { l.id == r.id }
func hash(into h: inout Hasher) { h.combine(id) }
}
let s: Set<Item> = [Item(id: 1, note: "x"), Item(id: 1, note: "y")]
print(s.count) // 1Быстрая проверка
Что ещё нужно сделать после написания пользовательского ==, использующего только некоторые поля?
Итоги
Вы изучили пользовательское равенство:
- Пишите собственный
==, когда стандартное сравнение всей структуры даёт неверный результат - Распространённые случаи: только идентификатор, сравнение без учёта регистра, числа с плавающей точкой с допуском, игнорирование кэша
- Согласуйте
hash(into:)с теми же полями - Сохраняйте рефлексивность, симметричность и транзитивность
Курс завершён! Далее: CustomStringConvertible и Mirror.
Часто задаваемые вопросы
Урок «Пользовательская семантика равенства» бесплатный?
Да — полный текст урока «Пользовательская семантика равенства» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.
Чему я научусь в уроке «Пользовательская семантика равенства»?
Реализуйте правила равенства, соответствующие предметной области. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Swift Academy?
Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Пользовательская семантика равенства»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Swift Academy?
Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Синтезированный Equatable
- Hashable и hash(into:)
- Comparable и сортировка
- Пользовательская семантика равенства