等価性とハッシュ
CoddyのRubyジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 37/57。
Rubyにはいくつかの種類の等価性があります。==は値の等価性で、クラスで定義します。ハッシュとuniqは、代わりに別の2つのメソッド、eql?と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)出力:
true
2
nilHashはまずhash値(オブジェクトから計算される数値)を比較し、次にeql?で候補を確認します。==と同じフィールドを使って両方を定義します。alias eql? ==は==を再利用し、[x, y].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
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])出力:
2
firstルールは次のとおりです。eql?で等しいオブジェクトは、同じhashを持たなければなりません。異なる2つのオブジェクトが偶然同じハッシュ値を持つこともあるため、eql?による確認が引き続き必要です。そうすれば、Hashによるカウントはオブジェクトの同一性ではなく値に基づいて機能します。
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}" }出力:
ruby: 3
gem: 1equal?は引き続き同一性を判定し、再定義してはいけません。組み込みの値はすでにこれらの規則に従っているため、文字列や配列をハッシュキーとして使用できます。
a == b # 同じ値(その意味は自分で定義する)
a.eql?(b) # Hash のキーと uniq において同じ値(== の別名)
a.hash # 数値。同じ値のオブジェクトは同じ数値を返す
a.equal?(b) # 同一のオブジェクト(再定義しないこと)チャレンジ
簡単座標が同じ点は==、eql?、hashで等しいと判定され、Hashのキーとして機能するように、クラスPointを完成させてください。to_sは(1, 2)を返します。
用意されたコードは、x,y形式の行で点を読み込みます。uniqで異なる点の数を出力し、次にHashで点ごとの訪問回数を数えて、最初に訪れた順に(1, 2): 2として出力します。最後に、新しいPoint.new(0, 0)がキーかどうかを出力します。
コードはpoint.rbに記述してください。main.rbには用意された入出力コードがあり、編集できません。
自分で試してみよう
require_relative 'point'
# 提供された入出力コード:そのままにしてください
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))}"
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ