Menu
CoddyTech

Igualdad y hash

Parte de la sección Programación orientada a objetos del Journey de Ruby de Coddy. Lección 37 de 57.

Ruby tiene varios tipos de igualdad. == es igualdad de valores, que defines para tu clase. Los hashes y uniq usan otros dos métodos: eql? y hash. Si solo defines ==, los puntos iguales siguen siendo claves distintas en un 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)

Salida:

true
2
nil

Un Hash compara primero los valores de hash, números calculados a partir de un objeto, y después comprueba los candidatos con eql?. Define ambos a partir de los mismos campos que ==. alias eql? == reutiliza ==, y [x, y].hash combina los campos:

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])

Salida:

2
first

La regla es: los objetos que son eql? deben tener el mismo hash. Dos objetos diferentes pueden compartir un valor hash por casualidad, por eso eql? sigue comprobándolo. Después, contar con un Hash funciona con valores en lugar de identidades:

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}" }

Salida:

ruby: 3
gem: 1

equal? sigue indicando identidad y nunca debería redefinirse. Los valores integrados ya siguen estas reglas, por eso las cadenas y los arrays funcionan como claves de hash:

a == b          # el mismo valor (lo defines tú)
a.eql?(b)       # el mismo valor para las claves de Hash y uniq (alias de ==)
a.hash          # un número; los objetos iguales producen números iguales
a.equal?(b)     # el mismo objeto (nunca lo redefinas)
challenge icon

Desafío

Fácil

Completa la clase Point para que los puntos con las mismas coordenadas sean iguales con ==, eql? y hash, y funcionen como claves de Hash. to_s devuelve (1, 2).

El código proporcionado lee los puntos en líneas con el formato x,y. Imprime el número de puntos distintos con uniq, después cuenta las visitas por punto en un Hash y las imprime en el orden de la primera visita como (1, 2): 2, y finalmente indica si un nuevo Point.new(0, 0) es una clave.

Tu código va en point.rb. main.rb contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

require_relative 'point'

# Código de entrada/salida proporcionado: mantenlo tal como está
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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Ruby online