Равенство и хеширование
Часть раздела Объектно-ориентированное программирование путешествия по 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 сравнивает значения 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? возвращает true, должны иметь одинаковый 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