Array#uniq in Ruby 1.8.7
On github: https://gist.github.com/1905081
class Equal
attr_accessor :name
def initialize(name)
@name = name
end
def inspect
@name
end
def hash
puts "#{name}: hash"
super
end
def eql?(co)
puts "#{name}: eql? against #{co.name}"
super
end
def ==(co)
puts "#{name}: == against #{co.name}"
super
end
end
EqualA = Equal.new('EqualA')
EqualB = Equal.new('EqualB')
EqualC = Equal.new('EqualC')
puts
def run(cmd)
puts "~~ #{cmd} ~~"
puts eval(cmd).inspect
puts
end
run '[EqualA, EqualB].uniq'
run '[EqualA, EqualB, EqualC].uniq'
class EqualHash < Equal
def hash
puts "#{name}: hash"
1000
end
end
EqualHashA = EqualHash.new('EqualHashA')
EqualHashB = EqualHash.new('EqualHashB')
EqualHashC = EqualHash.new('EqualHashC')
puts
run '[EqualHashA, EqualHashB].uniq'
run '[EqualHashA, EqualHashB, EqualHashC].uniq'
class EqualHashAndEql < EqualHash
def eql?(co)
puts "#{name}: eql? against #{co.name}"
true
end
end
EqualHashAndEqlA = EqualHashAndEql.new('EqualHashAndEqlA')
EqualHashAndEqlB = EqualHashAndEql.new('EqualHashAndEqlB')
EqualHashAndEqlC = EqualHashAndEql.new('EqualHashAndEqlC')
EqualHashAndEqlD = EqualHashAndEql.new('EqualHashAndEqlD')
puts
run '[EqualHashAndEqlA, EqualHashAndEqlB].uniq'
run '[EqualHashAndEqlA, EqualHashAndEqlB, EqualHashAndEqlC].uniq'
run '[EqualHashAndEqlA, EqualHashAndEqlB, EqualHashAndEqlC, EqualHashAndEqlD].uniq'
Equal is little more than a wrapper to Object to make things prettier. EqualHash is a subclass of Equal whose instances always return 1000 for #hash. EqualHashAndEql is a subclass of EqualHash whose instances always return true for #eql?.
Here’s the output with commentary:
~~ [EqualA, EqualB].uniq ~~
EqualA: hash
EqualB: hash
[EqualA, EqualB]
~~ [EqualA, EqualB, EqualC].uniq ~~
EqualA: hash
EqualB: hash
EqualC: hash
[EqualA, EqualB, EqualC]
#uniq internally calls #hash on each element, realizes they’re different and allows both in the resulting array.
~~ [EqualHashA, EqualHashB].uniq ~~
EqualHashA: hash
EqualHashB: hash
EqualHashB: eql? against EqualHashA
[EqualHashA, EqualHashB]
~~ [EqualHashA, EqualHashB, EqualHashC].uniq ~~
EqualHashA: hash
EqualHashB: hash
EqualHashB: eql? against EqualHashA
EqualHashC: hash
EqualHashC: eql? against EqualHashB
EqualHashC: eql? against EqualHashA
[EqualHashA, EqualHashB, EqualHashC]
#uniq sees the hash collisions and falls back to calling #eql? in a n^2 loop.
~~ [EqualHashAndEqlA, EqualHashAndEqlB].uniq ~~
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
EqualHashAndEqlB: eql? against EqualHashAndEqlA
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
[EqualHashAndEqlA]
~~ [EqualHashAndEqlA, EqualHashAndEqlB, EqualHashAndEqlC].uniq ~~
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
EqualHashAndEqlB: eql? against EqualHashAndEqlA
EqualHashAndEqlC: hash
EqualHashAndEqlC: eql? against EqualHashAndEqlA
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
EqualHashAndEqlC: hash
[EqualHashAndEqlA]
~~ [EqualHashAndEqlA, EqualHashAndEqlB, EqualHashAndEqlC, EqualHashAndEqlD].uniq ~~
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
EqualHashAndEqlB: eql? against EqualHashAndEqlA
EqualHashAndEqlC: hash
EqualHashAndEqlC: eql? against EqualHashAndEqlA
EqualHashAndEqlD: hash
EqualHashAndEqlD: eql? against EqualHashAndEqlA
EqualHashAndEqlA: hash
EqualHashAndEqlB: hash
EqualHashAndEqlC: hash
EqualHashAndEqlD: hash
[EqualHashAndEqlA]
Same as above, except that because #eql? returns true, B, C, and D are not added to the intermediate resulting array. I’m not entirely sure why #hash is called again once everything is done though.