Menu
Coddy logo textTech

Równość i hash

Część sekcji Programowanie obiektowe ścieżki Ruby w Coddy. Lekcja 37 z 57.

Ruby ma kilka rodzajów równości. == oznacza równość wartości, którą definiujesz dla swojej klasy. Skróty i uniq używają zamiast tego dwóch innych metod: eql? i hash. Gdy zdefiniowano tylko ==, równe punkty nadal są różnymi kluczami skrótu:

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)

Wynik:

true
2
nil

Hash najpierw porównuje wartości hash, czyli liczby obliczone na podstawie obiektu, a następnie sprawdza kandydatów za pomocą eql?. Zdefiniuj obie metody na podstawie tych samych pól co ==. alias eql? == ponownie wykorzystuje ==, a [x, y].hash łączy pola:

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

Wynik:

2
first

Reguła jest następująca: obiekty, dla których eql? zwraca prawdę, muszą mieć tę samą wartość hash. Dwa różne obiekty mogą przypadkowo mieć tę samą wartość skrótu, dlatego eql? nadal sprawdza ich równość. Dzięki temu zliczanie za pomocą Hash działa na wartościach zamiast na tożsamości obiektów:

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

Wynik:

ruby: 3
gem: 1

equal? nadal oznacza tożsamość i nigdy nie należy go redefiniować. Wartości wbudowane już przestrzegają tych zasad, dlatego ciągi znaków i tablice działają jako klucze hasza:

a == b          # ta sama wartość (sam ją definiujesz)
a.eql?(b)       # ta sama wartość dla kluczy Hash i uniq (alias ==)
a.hash          # liczba; równe obiekty dają równe liczby
a.equal?(b)     # ten sam obiekt (nigdy nie redefiniuj)
challenge icon

Wyzwanie

Łatwy

Uzupełnij klasę Point, aby punkty o tych samych współrzędnych były równe przy użyciu ==, eql? i hash oraz mogły być kluczami Hasha. to_s zwraca (1, 2).

Dostarczony kod odczytuje punkty z wierszy w formacie x,y. Wypisuje liczbę różnych punktów za pomocą uniq, a następnie zlicza odwiedziny każdego punktu w Hashu i wypisuje je w kolejności pierwszych odwiedzin jako (1, 2): 2, a na końcu informuje, czy nowy Point.new(0, 0) jest kluczem.

Umieść swój kod w pliku point.rb. Plik main.rb zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.

Spróbuj swoich sił

require_relative 'point'

# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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 iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Ruby online