Menu
CoddyTech

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. Hashy i uniq używają zamiast tego dwóch innych metod: eql? i hash. Gdy zdefiniowane jest tylko ==, równe punkty nadal są różnymi kluczami hashy:

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

Zasada jest taka: obiekty, które są eql?, muszą mieć ten sam hash. Dwa różne obiekty mogą przypadkowo mieć tę samą wartość skrótu, dlatego eql? nadal sprawdza równość. Wtedy zliczanie za pomocą Hash działa na wartościach, a nie 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 sprawdza tożsamość i nigdy nie należy go redefiniować. Wbudowane wartości już przestrzegają tych reguł, dlatego łańcuchy i tablice mogą być kluczami Hash:

a == b          # ta sama wartość (definiujesz ją samodzielnie)
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, tak aby punkty o tych samych współrzędnych były równe za pomocą ==, eql? i hash oraz działały jako klucze Hash. to_s zwraca (1, 2).

Dostarczony kod odczytuje punkty z wierszy w formacie x,y. Wypisuje liczbę różnych punktów za pomocą uniq, następnie zlicza odwiedziny każdego punktu w Hash 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.

Twój kod umieść w pliku point.rb. 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