Menu
Coddy logo textTech

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
nil

A 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
first

The 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: 1

equal? 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 icon

Challenge

Easy

Complete 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))}"
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online Ruby compiler