Равенство и хеширование
Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 37 из 57.
В Ruby есть несколько видов равенства. == — это равенство значений, которое вы определяете для своего класса. Хэши и uniq вместо него используют два других метода: eql? и hash. Если определён только ==, равные точки всё ещё являются разными ключами хэша:
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def ==(other)
other.is_a?(Point) && x == other.x && y == other.y
end
end
a = Point.new(1, 2)
b = Point.new(1, 2)
puts a == b
puts [a, b].uniq.size
puts({ a => "first" }[b].inspect)Результат:
true
2
nilХеш сначала сравнивает значения hash — числа, вычисленные на основе объекта, — а затем проверяет кандидатов с помощью eql?. Определите оба метода на основе тех же полей, что и ==. alias eql? == повторно использует ==, а [x, y].hash объединяет поля:
class Point
attr_reader :x, :y
def initialize(x, y)
@x = x
@y = y
end
def ==(other)
other.is_a?(Point) && x == other.x && y == other.y
end
alias eql? ==
def hash
[x, y].hash
end
end
a = Point.new(1, 2)
b = Point.new(1, 2)
puts [a, b, Point.new(3, 4)].uniq.size
puts({ a => "first" }[b])Вывод:
2
firstПравило таково: объекты, для которых eql? возвращает истину, должны иметь одинаковый hash. Два разных объекта могут случайно иметь одинаковое значение хеша, поэтому eql? всё равно выполняет проверку. Тогда подсчёт с помощью Hash работает со значениями, а не с идентичностями:
class Word
attr_reader :text
def initialize(text)
@text = text.downcase
end
def ==(other)
other.is_a?(Word) && text == other.text
end
alias eql? ==
def hash
text.hash
end
end
counts = Hash.new(0)
%w[Ruby ruby RUBY gem].each { |t| counts[Word.new(t)] += 1 }
counts.each { |word, n| puts "#{word.text}: #{n}" }Результат:
ruby: 3
gem: 1equal? сохраняет идентичность и никогда не должен переопределяться. Встроенные значения уже следуют этим правилам, поэтому строки и массивы работают как ключи хеша:
a == b # одинаковое значение (вы определяете его)
a.eql?(b) # одинаковое значение для ключей Hash и uniq (алиас ==)
a.hash # число; равные объекты дают равные числа
a.equal?(b) # тот же объект (никогда не переопределяйте)Задание
ЛегкоЗавершите класс Point, чтобы точки с одинаковыми координатами считались равными с помощью ==, eql? и hash и могли использоваться как ключи Hash. to_s возвращает (1, 2).
Предоставленный код считывает точки в виде строк x,y. Он выводит количество различных точек с помощью uniq, затем подсчитывает посещения каждой точки в Hash и выводит их в порядке первого посещения в виде (1, 2): 2, а в конце — является ли новый Point.new(0, 0) ключом.
Ваш код должен находиться в point.rb. В main.rb находится предоставленный код ввода/вывода, и его нельзя изменять.
Попробуйте сами
require_relative 'point'
# Предоставленный код ввода/вывода: оставьте его как есть
input = []
while (line = gets)
input << line.chomp
end
points = input.map { |line| Point.new(*line.split(",").map(&:to_i)) }
puts "distinct: #{points.uniq.size}"
visits = Hash.new(0)
points.each { |pt| visits[pt] += 1 }
visits.each { |pt, n| puts "#{pt}: #{n}" }
puts "origin visited: #{visits.key?(Point.new(0, 0))}"
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПеременные экземпляраМетоды и selfto_s и inspectПовторение — читательский билет4Инкапсуляция
Приватные методыЗащищённые методыПриватные сеттерыЗащита состоянияПовторение — торговый автомат7Полиморфизм и утиная типизация
ПолиморфизмУтиная типизацияАбстрактные методыcase и классыПовторение — способы оплаты10Шаблоны проектирования, часть 2
Делегирование с помощью ForwardableШаблон декоратораШаблон строителяШаблон состояния2Аксессоры и self
Чтение и записьПользовательские сеттерыself в методахИдентичность и равенствоПовторение — список контактов5Наследование
ПодклассыПереопределение методовВызов superis_a? и предкиПовторение — расчёт зарплаты сотрудников8Специальные методы
Равенство и хешированиеМетоды операторовИндексация и добавлениеПользовательские коллекцииПовторение — дроби11Проект: Управление библиотекой
Книги и читателиВыдача книг3Методы и переменные классов
Методы классовПеременные экземпляра классаКонстантыФабричные методыПовторение — коды купонов6Модули и примеси
Модули и includeРасширение объектовМодуль ComparableМодуль EnumerableПовторение — спортивная лига9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Онлайн-компилятор Ruby