Equality and hash
Part of the Object Oriented Programming section of Coddy's Ruby journey. Lesson 37 of 57.
Ruby has several kinds of equality. == is value equality, which you define for your class. Hashes and uniq use two other methods instead: eql? and hash. With only == defined, equal points are still different hash keys:
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)Output:
true
2
nilA Hash first compares hash values, numbers computed from an object, and then checks candidates with eql?. Define both, from the same fields as ==. alias eql? == reuses ==, and [x, y].hash combines the fields:
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])Output:
2
firstThe rule is: objects that are eql? must have the same hash. Two different objects may share a hash value by chance, which is why eql? still checks. Then counting with a Hash works on values instead of identities:
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}" }Output:
ruby: 3
gem: 1equal? remains identity and should never be redefined. Built-in values already follow these rules, which is why strings and arrays work as hash keys:
a == b # same value (you define it)
a.eql?(b) # same value for Hash keys and uniq (alias of ==)
a.hash # a number; equal objects give equal numbers
a.equal?(b) # the same object (never redefine)Challenge
EasyComplete the class Point so points with the same coordinates are equal with ==, eql? and hash, and work as Hash keys. to_s returns (1, 2).
The supplied code reads points as x,y lines. It prints the number of distinct points with uniq, then counts the visits per point in a Hash and prints them in first-visit order as (1, 2): 2, and finally whether a new Point.new(0, 0) is a key.
Your code goes in point.rb. main.rb holds the supplied input/output code and cannot be edited.
Try it yourself
require_relative 'point'
# Supplied input/output code: keep it as it is
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))}"
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Classes and Objects
Working With FilesDefining ClassesInstance VariablesMethods and selfto_s and inspectRecap - Library Card7Polymorphism and Duck Typing
PolymorphismDuck TypingAbstract Methodscase and ClassesRecap - Payment Methods2Accessors and Self
Readers and WritersCustom Settersself in MethodsIdentity and EqualityRecap - Contact List8Special Methods
Equality and hashOperator MethodsIndexing and AppendingCustom CollectionsRecap - Fractions11Project: Library Management
Books and MembersBorrowing Books3Class Methods and Variables
Class MethodsClass Instance VariablesConstantsFactory MethodsRecap - Coupon CodesPractice on your own: Online Ruby compiler