Menu
Coddy logo textTech

Gleichheit und Hash

Teil des Abschnitts Objektorientierte Programmierung der Ruby-Journey von Coddy. Lektion 37 von 57.

Ruby hat mehrere Arten von Gleichheit. == ist Wertgleichheit, die du für deine Klasse definierst. Hashes und uniq verwenden stattdessen zwei andere Methoden: eql? und hash. Wenn nur == definiert ist, sind gleiche Punkte weiterhin unterschiedliche Hash-Schlüssel:

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)

Ausgabe:

true
2
nil

Ein Hash vergleicht zuerst hash-Werte, also aus einem Objekt berechnete Zahlen, und prüft dann die Kandidaten mit eql?. Definiere beide anhand derselben Felder wie ==. alias eql? == verwendet == wieder, und [x, y].hash kombiniert die Felder:

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

Ausgabe:

2
first

Die Regel lautet: Objekte, die eql? sind, müssen denselben hash haben. Zwei verschiedene Objekte können zufällig denselben Hash-Wert haben, weshalb eql? weiterhin prüft. Das Zählen mit einem Hash funktioniert dann anhand von Werten statt Identitäten:

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

Ausgabe:

ruby: 3
gem: 1

equal? bleibt die Identität und sollte niemals neu definiert werden. Integrierte Werte folgen diesen Regeln bereits, weshalb Strings und Arrays als Hash-Schlüssel funktionieren:

a == b          # gleicher Wert (du definierst ihn)
a.eql?(b)       # gleicher Wert für Hash-Schlüssel und uniq (Alias von ==)
a.hash          # eine Zahl; gleiche Objekte ergeben gleiche Zahlen
a.equal?(b)     # dasselbe Objekt (niemals neu definieren)
challenge icon

Aufgabe

Einfach

Vervollständige die Klasse Point, sodass Punkte mit denselben Koordinaten mit ==, eql? und hash gleich sind und als Hash-Schlüssel funktionieren. to_s gibt (1, 2) zurück.

Der bereitgestellte Code liest Punkte als Zeilen im Format x,y ein. Er gibt mit uniq die Anzahl der unterschiedlichen Punkte aus, zählt dann die Besuche pro Punkt in einem Hash und gibt sie in der Reihenfolge des ersten Besuchs als (1, 2): 2 aus und schließlich, ob ein neuer Point.new(0, 0) ein Schlüssel ist.

Dein Code gehört in point.rb. main.rb enthält den bereitgestellten Eingabe-/Ausgabecode und darf nicht bearbeitet werden.

Probier es selbst

require_relative 'point'

# Gelieferter Ein-/Ausgabe-Code: belasse ihn wie er ist
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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-Ruby-Compiler