連想配列の再発明(線形検索、二分探索木、ハッシュテーブル)してそれぞれの時間計測

class MyHash1
	include Enumerable
	
	Pair = Struct.new( :key, :value )
	
	def initialize
		@table = []
	end
	
	def []( key )
		@table.each do |pair|
			return pair.value if pair.key.eql?( key )
		end
		return nil
	end
	
	def []=( key, value )
		@table.each do |pair|
			if pair.key.eql?( key )
				pair.value = value
				return value
			end
		end
		@table << Pair.new( key, value )
		return value
	end
	
	def delete( key )
		@table.each_with_index do |pair,i|
			if pair.key.eql?( key )
				value = pair.value
				@table.delete_at( i )
				return value
			end
		end
		return nil
	end
	
	def each
		@table.each do |pair|
			yield [pair.key, pair.value]
		end
	end
	
	def inspect
		'{' + map{|key,value|
			key.inspect+'=>'+value.inspect}.join(', ') + '}'
	end
end

class MyHash2
	include Enumerable
	
	#Node = Struct.new( :key, :hash, :value, :left, :right )
	class Node
		def initialize( key, value, left = nil, right = nil )
			@key = key
			@value = value
			@left = left
			@right = right
		end
		
		attr_accessor :key, :value, :left, :right
		
		def each
			@left.each{|v| yield v } if @left
			yield [key, value]
			@right.each{|v| yield v } if @right
		end
		
		def inspect( level = 0 )
			puts ' ' * level + key.inspect + '(' + value.inspect + ')'
			if @left
				puts ' ' * level + 'left:'
				@left.inspect( level + 1 )
			end
			if @right
				puts ' ' * level + 'right:'
				@right.inspect( level + 1 )
			end
		end
	end
	
	def initialize
		@tree = nil
	end
	
	attr_reader :tree
	
	def []( key )
		hash = key.hash
		node = @tree
		loop do
			return nil unless node
			return node.value if node.key.hash == hash && node.key.eql?( key )
			if hash < node.key.hash
				node = node.left
			else
				node = node.right
			end
		end
		return nil
	end
	
	def []=( key, value )
		hash = key.hash
		unless @tree
			@tree = Node.new( key, value )
			return value
		end
		node = @tree
		loop do
			if node.key.hash == hash && node.key.eql?( key )
				node.value = value
				return value
			end
			if hash < node.key.hash
				unless node.left
					node.left = Node.new( key, value )
					return value
				else
					node = node.left
				end
			else
				unless node.right
					node.right = Node.new( key, value )
					return value
				else
					node = node.right
				end
			end
		end
	end
	
	def delete( key )
		hash = key.hash
		node = @tree
		parent = nil
		set_node = proc{|v| @tree = v }
		loop do
			return nil unless node
			if node.key.hash == hash && node.key.eql?( key )
				value = node.value
				if node.left
					other = node.right
					set_node.call( node.left )
					node = node.left
					if other
						n = node
						n = n.right while n.right
						n.right = other
					end
				elsif node.right
					set_node.call( node.right )
				else
					set_node.call( nil )
				end
				return value
			end
			if hash < node.key.hash
				parent = node
				set_node = proc{|v| parent.left = v }
				node = node.left
			else
				parent = node
				set_node = proc{|v| parent.right = v }
				node = node.right
			end
		end
	end
	
	def each
		@tree.each{|v| yield v } if @tree
	end
	
	def inspect
		'{' + map{|key,value|
			key.inspect+'=>'+value.inspect}.join(', ') + '}'
	end
end

class MyHash3
	include Enumerable
	
	Pair = Struct.new( :key, :value )
	
	def initialize( size = 3 )
		@num = 0
		@table = Array.new( size )
	end

	def []( key )
		index = key.hash % @table.size
		list =  @table[index]
		return nil unless list
		list.each do |pair|
			return pair.value if key.eql?( pair.key )
		end
		return nil
	end
	
	def rehash
		new_hash = self.class.new( @table.size * 2 + 1 )
		each do |key,value|
			new_hash[key] = value
		end
		@table = new_hash.instance_variable_get( :@table )
		@num = new_hash.instance_variable_get( :@num )
	end
	
	def []=( key, value )
		index = key.hash % @table.size
		list =  @table[index]
		unless list
			@table[index] = [ Pair.new( key, value ) ]
		else
			list.each do |pair|
				if key.eql?( pair.key )
					pair.value = value
					return value
				end
			end
			list << Pair.new( key, value )
		end
		@num += 1
		rehash if @num.to_f / @table.size >= 3.0 / 4
		return value
	end
	
	def delete( key )
		index = key.hash % @table.size
		list =  @table[index]
		return nil unless list
		list.each_with_index do |pair,i|
			if key.eql?( pair.key )
				value = pair.value
				list.delete_at( i )
				@num -= 1
				return value
			end
		end
		return nil
	end
	
	def each
		@table.each do |list|
			next unless list
			list.each do |pair|
				yield [pair.key, pair.value]
			end
		end
	end
	
	def inspect
		'{' + map{|key,value|
			key.inspect+'=>'+value.inspect}.join(', ') + '}'
	end
end

require 'benchmark'

class_list = [Hash, MyHash1, MyHash2, MyHash3]

Benchmark.bm(10) do |x|
	class_list.each do |c|
		srand 0
		hash = c.new
		x.report( c.name ) do
			10000.times do |i|
				case i % 3
				when 0
					hash[rand(1e5)] = i
				when 1
					hash[rand(1e5)]
				when 2
					hash.delete( rand(1e5) )
				end
			end
		end
		#puts "#{hash.to_a.size} #{hash.to_a.sort.hash}"
	end
end
                user     system      total        real
Hash        0.031000   0.000000   0.031000 (  0.031000)
MyHash1    53.016000   0.047000  53.063000 ( 53.547000)
MyHash2     2.312000   0.000000   2.312000 (  2.312000)
MyHash3     0.406000   0.000000   0.406000 (  0.406000)

MyHash1 が線形検索、MyHash2が二分探索木、MyHash3がハッシュテーブル。
ハッシュテーブルの実装は404 Blog Not Found:アルゴリズム百選 - ハッシュを再発明するを参考に。
二分探索木の実装は、二分探索木 - Wikipediaを参考に。要素の削除は自分で考えた。
平衡2分探索木とやらを使えばもっと効率がいいみたいなので試したい。