# # A Regex-queryable Hash. # # Usage: # # greeting = Rash.new( /^Mr./ => "Hello sir!", /^Mrs./ => "Evening, madame." ) # greeting["Mr. Steve Austin"] #=> "Hello sir!" # greeting["Mrs. Steve Austin"] #=> "Evening, madame." # class Rash attr_accessor :optimize_every def initialize(initial) @hash = {} @regexes = [] @ranges = [] @regex_counts = Hash.new(0) @optimize_every = 500 @lookups = 0 update(initial) end def update(other) for key, value in other self[key] = value end self end def []=(key, value) case key when Regexp #key = normalize_regex(key) # this used to just do: /#{regexp}/ @regexes << key when Range @ranges << key end @hash[key] = value end # # Return the first thing that matches the key. # def [](key) all(key).first end # # Return everything that matches the query. # def all(query) return to_enum(:all, query) unless block_given? if @hash.include? query yield @hash[query] return end case query when String optimize_if_necessary! @regexes.each do |regex| if match = regex.match(query) @regex_counts[regex] += 1 value = @hash[regex] if value.responds_to? :call yield value.call(match) else yield value end end end when Integer @ranges.each do |range| yield @hash[range] if range.include? query end when Regexp # TODO: this doesn't seem very useful. should I ditch it? let me know! @hash.each do |key,val| yield val if key.is_a? String and query =~ key end end end def method_missing(*args, &block) @hash.send(*args, &block) end private def optimize_if_necessary! if (@lookups += 1) >= @optimize_every @regexes = @regex_counts.sort_by { |regex,count| -count }.map { |regex,count| regex } @lookups = 0 end end end